feat: Implementação de aparelhos e anexos
This commit is contained in:
parent
355267b740
commit
3e6319566b
|
|
@ -4,13 +4,16 @@ using line_gestao_api.Dtos;
|
||||||
using line_gestao_api.Models;
|
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.Hosting;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.StaticFiles;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
@ -28,6 +31,8 @@ namespace line_gestao_api.Controllers
|
||||||
private readonly IVigenciaNotificationSyncService _vigenciaNotificationSyncService;
|
private readonly IVigenciaNotificationSyncService _vigenciaNotificationSyncService;
|
||||||
private readonly ParcelamentosImportService _parcelamentosImportService;
|
private readonly ParcelamentosImportService _parcelamentosImportService;
|
||||||
private readonly SpreadsheetImportAuditService _spreadsheetImportAuditService;
|
private readonly SpreadsheetImportAuditService _spreadsheetImportAuditService;
|
||||||
|
private readonly string _aparelhoAttachmentsRootPath;
|
||||||
|
private static readonly FileExtensionContentTypeProvider FileContentTypeProvider = new();
|
||||||
private static readonly List<AccountCompanyDto> AccountCompanies = new()
|
private static readonly List<AccountCompanyDto> AccountCompanies = new()
|
||||||
{
|
{
|
||||||
new AccountCompanyDto
|
new AccountCompanyDto
|
||||||
|
|
@ -57,13 +62,15 @@ namespace line_gestao_api.Controllers
|
||||||
ITenantProvider tenantProvider,
|
ITenantProvider tenantProvider,
|
||||||
IVigenciaNotificationSyncService vigenciaNotificationSyncService,
|
IVigenciaNotificationSyncService vigenciaNotificationSyncService,
|
||||||
ParcelamentosImportService parcelamentosImportService,
|
ParcelamentosImportService parcelamentosImportService,
|
||||||
SpreadsheetImportAuditService spreadsheetImportAuditService)
|
SpreadsheetImportAuditService spreadsheetImportAuditService,
|
||||||
|
IWebHostEnvironment webHostEnvironment)
|
||||||
{
|
{
|
||||||
_db = db;
|
_db = db;
|
||||||
_tenantProvider = tenantProvider;
|
_tenantProvider = tenantProvider;
|
||||||
_vigenciaNotificationSyncService = vigenciaNotificationSyncService;
|
_vigenciaNotificationSyncService = vigenciaNotificationSyncService;
|
||||||
_parcelamentosImportService = parcelamentosImportService;
|
_parcelamentosImportService = parcelamentosImportService;
|
||||||
_spreadsheetImportAuditService = spreadsheetImportAuditService;
|
_spreadsheetImportAuditService = spreadsheetImportAuditService;
|
||||||
|
_aparelhoAttachmentsRootPath = Path.Combine(webHostEnvironment.ContentRootPath, "uploads", "aparelhos");
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ImportExcelForm
|
public class ImportExcelForm
|
||||||
|
|
@ -71,6 +78,12 @@ namespace line_gestao_api.Controllers
|
||||||
public IFormFile File { get; set; } = default!;
|
public IFormFile File { get; set; } = default!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class UploadAparelhoAnexosForm
|
||||||
|
{
|
||||||
|
public IFormFile? NotaFiscal { get; set; }
|
||||||
|
public IFormFile? Recibo { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
// ==========================================================
|
// ==========================================================
|
||||||
// ✅ 1. ENDPOINT: AGRUPAR POR CLIENTE
|
// ✅ 1. ENDPOINT: AGRUPAR POR CLIENTE
|
||||||
// ==========================================================
|
// ==========================================================
|
||||||
|
|
@ -523,6 +536,10 @@ namespace line_gestao_api.Controllers
|
||||||
line.Chip,
|
line.Chip,
|
||||||
Cliente = clienteExibicao,
|
Cliente = clienteExibicao,
|
||||||
line.Usuario,
|
line.Usuario,
|
||||||
|
line.CentroDeCustos,
|
||||||
|
SetorNome = line.Setor != null ? line.Setor.Nome : null,
|
||||||
|
AparelhoNome = line.Aparelho != null ? line.Aparelho.Nome : null,
|
||||||
|
AparelhoCor = line.Aparelho != null ? line.Aparelho.Cor : null,
|
||||||
line.PlanoContrato,
|
line.PlanoContrato,
|
||||||
line.Status,
|
line.Status,
|
||||||
line.Skil,
|
line.Skil,
|
||||||
|
|
@ -588,6 +605,10 @@ namespace line_gestao_api.Controllers
|
||||||
Chip = x.Chip,
|
Chip = x.Chip,
|
||||||
Cliente = x.Cliente,
|
Cliente = x.Cliente,
|
||||||
Usuario = x.Usuario,
|
Usuario = x.Usuario,
|
||||||
|
CentroDeCustos = x.CentroDeCustos,
|
||||||
|
SetorNome = x.SetorNome,
|
||||||
|
AparelhoNome = x.AparelhoNome,
|
||||||
|
AparelhoCor = x.AparelhoCor,
|
||||||
PlanoContrato = x.PlanoContrato,
|
PlanoContrato = x.PlanoContrato,
|
||||||
Status = x.Status,
|
Status = x.Status,
|
||||||
Skil = x.Skil,
|
Skil = x.Skil,
|
||||||
|
|
@ -657,6 +678,10 @@ namespace line_gestao_api.Controllers
|
||||||
Chip = x.Chip,
|
Chip = x.Chip,
|
||||||
Cliente = x.Cliente,
|
Cliente = x.Cliente,
|
||||||
Usuario = x.Usuario,
|
Usuario = x.Usuario,
|
||||||
|
CentroDeCustos = x.CentroDeCustos,
|
||||||
|
SetorNome = x.Setor != null ? x.Setor.Nome : null,
|
||||||
|
AparelhoNome = x.Aparelho != null ? x.Aparelho.Nome : null,
|
||||||
|
AparelhoCor = x.Aparelho != null ? x.Aparelho.Cor : null,
|
||||||
PlanoContrato = x.PlanoContrato,
|
PlanoContrato = x.PlanoContrato,
|
||||||
Status = x.Status,
|
Status = x.Status,
|
||||||
Skil = x.Skil,
|
Skil = x.Skil,
|
||||||
|
|
@ -688,7 +713,11 @@ namespace line_gestao_api.Controllers
|
||||||
[HttpGet("{id:guid}")]
|
[HttpGet("{id:guid}")]
|
||||||
public async Task<ActionResult<MobileLineDetailDto>> GetById(Guid id)
|
public async Task<ActionResult<MobileLineDetailDto>> GetById(Guid id)
|
||||||
{
|
{
|
||||||
var x = await _db.MobileLines.AsNoTracking().FirstOrDefaultAsync(a => a.Id == id);
|
var x = await _db.MobileLines
|
||||||
|
.AsNoTracking()
|
||||||
|
.Include(a => a.Setor)
|
||||||
|
.Include(a => a.Aparelho)
|
||||||
|
.FirstOrDefaultAsync(a => a.Id == id);
|
||||||
if (x == null) return NotFound();
|
if (x == null) return NotFound();
|
||||||
var vigencia = await FindVigenciaByMobileLineAsync(x, null, asNoTracking: true);
|
var vigencia = await FindVigenciaByMobileLineAsync(x, null, asNoTracking: true);
|
||||||
return Ok(ToDetailDto(x, vigencia));
|
return Ok(ToDetailDto(x, vigencia));
|
||||||
|
|
@ -790,6 +819,8 @@ namespace line_gestao_api.Controllers
|
||||||
lineToPersist.Cliente = ensuredTenant.NomeOficial;
|
lineToPersist.Cliente = ensuredTenant.NomeOficial;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await ApplySetorAndAparelhoToLineAsync(lineToPersist, req);
|
||||||
|
|
||||||
var vigencia = await UpsertVigenciaFromMobileLineAsync(
|
var vigencia = await UpsertVigenciaFromMobileLineAsync(
|
||||||
lineToPersist,
|
lineToPersist,
|
||||||
req.DtEfetivacaoServico,
|
req.DtEfetivacaoServico,
|
||||||
|
|
@ -923,6 +954,7 @@ namespace line_gestao_api.Controllers
|
||||||
Linha = linhaLimpa,
|
Linha = linhaLimpa,
|
||||||
Chip = string.IsNullOrWhiteSpace(chipLimpo) ? null : chipLimpo,
|
Chip = string.IsNullOrWhiteSpace(chipLimpo) ? null : chipLimpo,
|
||||||
Usuario = entry.Usuario?.Trim(),
|
Usuario = entry.Usuario?.Trim(),
|
||||||
|
CentroDeCustos = NormalizeOptionalText(entry.CentroDeCustos),
|
||||||
Status = entry.Status?.Trim(),
|
Status = entry.Status?.Trim(),
|
||||||
Skil = entry.Skil?.Trim(),
|
Skil = entry.Skil?.Trim(),
|
||||||
Modalidade = entry.Modalidade?.Trim(),
|
Modalidade = entry.Modalidade?.Trim(),
|
||||||
|
|
@ -973,6 +1005,8 @@ namespace line_gestao_api.Controllers
|
||||||
newLine.Cliente = ensuredTenant.NomeOficial;
|
newLine.Cliente = ensuredTenant.NomeOficial;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await ApplySetorAndAparelhoToLineAsync(newLine, entry);
|
||||||
|
|
||||||
_db.MobileLines.Add(newLine);
|
_db.MobileLines.Add(newLine);
|
||||||
|
|
||||||
var vigencia = await UpsertVigenciaFromMobileLineAsync(
|
var vigencia = await UpsertVigenciaFromMobileLineAsync(
|
||||||
|
|
@ -1293,11 +1327,49 @@ namespace line_gestao_api.Controllers
|
||||||
// ✅ 6. UPDATE
|
// ✅ 6. UPDATE
|
||||||
// ==========================================================
|
// ==========================================================
|
||||||
[HttpPut("{id:guid}")]
|
[HttpPut("{id:guid}")]
|
||||||
[Authorize(Roles = "sysadmin,gestor")]
|
[Authorize(Roles = "sysadmin,gestor,cliente")]
|
||||||
public async Task<IActionResult> Update(Guid id, [FromBody] UpdateMobileLineRequest req)
|
public async Task<IActionResult> Update(Guid id, [FromBody] UpdateMobileLineRequest req)
|
||||||
{
|
{
|
||||||
var x = await _db.MobileLines.FirstOrDefaultAsync(a => a.Id == id);
|
var x = await _db.MobileLines
|
||||||
|
.Include(a => a.Setor)
|
||||||
|
.Include(a => a.Aparelho)
|
||||||
|
.FirstOrDefaultAsync(a => a.Id == id);
|
||||||
if (x == null) return NotFound();
|
if (x == null) return NotFound();
|
||||||
|
|
||||||
|
var canManageFullLine = User.IsInRole(AppRoles.SysAdmin) || User.IsInRole(AppRoles.Gestor);
|
||||||
|
if (!canManageFullLine)
|
||||||
|
{
|
||||||
|
x.Usuario = NormalizeOptionalText(req.Usuario);
|
||||||
|
x.CentroDeCustos = NormalizeOptionalText(req.CentroDeCustos);
|
||||||
|
await ApplyAparelhoToLineAsync(
|
||||||
|
x,
|
||||||
|
x.TenantId,
|
||||||
|
req.AparelhoId,
|
||||||
|
req.AparelhoNome,
|
||||||
|
req.AparelhoCor,
|
||||||
|
req.AparelhoImei);
|
||||||
|
|
||||||
|
await UpsertVigenciaFromMobileLineAsync(
|
||||||
|
x,
|
||||||
|
dtEfetivacaoServico: null,
|
||||||
|
dtTerminoFidelizacao: null,
|
||||||
|
overrideDates: false,
|
||||||
|
previousLinha: null);
|
||||||
|
x.UpdatedAt = DateTime.UtcNow;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _db.SaveChangesAsync();
|
||||||
|
await _vigenciaNotificationSyncService.SyncCurrentTenantAsync();
|
||||||
|
}
|
||||||
|
catch (DbUpdateException)
|
||||||
|
{
|
||||||
|
return Conflict(new { message = "Conflito ao salvar." });
|
||||||
|
}
|
||||||
|
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
var previousLinha = x.Linha;
|
var previousLinha = x.Linha;
|
||||||
var previousCliente = x.Cliente;
|
var previousCliente = x.Cliente;
|
||||||
|
|
||||||
|
|
@ -1314,9 +1386,10 @@ namespace line_gestao_api.Controllers
|
||||||
var newChip = OnlyDigits(req.Chip);
|
var newChip = OnlyDigits(req.Chip);
|
||||||
x.Chip = string.IsNullOrWhiteSpace(newChip) ? null : newChip;
|
x.Chip = string.IsNullOrWhiteSpace(newChip) ? null : newChip;
|
||||||
|
|
||||||
x.Cliente = req.Cliente?.Trim();
|
x.Cliente = NormalizeOptionalText(req.Cliente);
|
||||||
x.Usuario = req.Usuario?.Trim();
|
x.Usuario = NormalizeOptionalText(req.Usuario);
|
||||||
x.PlanoContrato = req.PlanoContrato?.Trim();
|
x.CentroDeCustos = NormalizeOptionalText(req.CentroDeCustos);
|
||||||
|
x.PlanoContrato = NormalizeOptionalText(req.PlanoContrato);
|
||||||
x.FranquiaVivo = req.FranquiaVivo;
|
x.FranquiaVivo = req.FranquiaVivo;
|
||||||
x.ValorPlanoVivo = req.ValorPlanoVivo;
|
x.ValorPlanoVivo = req.ValorPlanoVivo;
|
||||||
x.GestaoVozDados = req.GestaoVozDados;
|
x.GestaoVozDados = req.GestaoVozDados;
|
||||||
|
|
@ -1332,16 +1405,16 @@ namespace line_gestao_api.Controllers
|
||||||
x.ValorContratoLine = req.ValorContratoLine;
|
x.ValorContratoLine = req.ValorContratoLine;
|
||||||
x.Desconto = req.Desconto;
|
x.Desconto = req.Desconto;
|
||||||
x.Lucro = req.Lucro;
|
x.Lucro = req.Lucro;
|
||||||
x.Status = req.Status?.Trim();
|
x.Status = NormalizeOptionalText(req.Status);
|
||||||
x.DataBloqueio = ToUtc(req.DataBloqueio);
|
x.DataBloqueio = ToUtc(req.DataBloqueio);
|
||||||
x.Skil = req.Skil?.Trim();
|
x.Skil = NormalizeOptionalText(req.Skil);
|
||||||
x.Modalidade = req.Modalidade?.Trim();
|
x.Modalidade = NormalizeOptionalText(req.Modalidade);
|
||||||
x.Cedente = req.Cedente?.Trim();
|
x.Cedente = NormalizeOptionalText(req.Cedente);
|
||||||
x.Solicitante = req.Solicitante?.Trim();
|
x.Solicitante = NormalizeOptionalText(req.Solicitante);
|
||||||
x.DataEntregaOpera = ToUtc(req.DataEntregaOpera);
|
x.DataEntregaOpera = ToUtc(req.DataEntregaOpera);
|
||||||
x.DataEntregaCliente = ToUtc(req.DataEntregaCliente);
|
x.DataEntregaCliente = ToUtc(req.DataEntregaCliente);
|
||||||
x.VencConta = req.VencConta?.Trim();
|
x.VencConta = NormalizeOptionalText(req.VencConta);
|
||||||
x.TipoDeChip = req.TipoDeChip?.Trim();
|
x.TipoDeChip = NormalizeOptionalText(req.TipoDeChip);
|
||||||
|
|
||||||
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);
|
||||||
|
|
@ -1371,6 +1444,15 @@ namespace line_gestao_api.Controllers
|
||||||
x.Cliente = ensuredTenant.NomeOficial;
|
x.Cliente = ensuredTenant.NomeOficial;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await ApplySetorToLineAsync(x, x.TenantId, req.SetorId, req.SetorNome);
|
||||||
|
await ApplyAparelhoToLineAsync(
|
||||||
|
x,
|
||||||
|
x.TenantId,
|
||||||
|
req.AparelhoId,
|
||||||
|
req.AparelhoNome,
|
||||||
|
req.AparelhoCor,
|
||||||
|
req.AparelhoImei);
|
||||||
|
|
||||||
await UpsertVigenciaFromMobileLineAsync(
|
await UpsertVigenciaFromMobileLineAsync(
|
||||||
x,
|
x,
|
||||||
req.DtEfetivacaoServico,
|
req.DtEfetivacaoServico,
|
||||||
|
|
@ -1389,6 +1471,128 @@ namespace line_gestao_api.Controllers
|
||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("{id:guid}/aparelho/anexos")]
|
||||||
|
[Authorize(Roles = "sysadmin,gestor,cliente")]
|
||||||
|
[Consumes("multipart/form-data")]
|
||||||
|
[RequestSizeLimit(25_000_000)]
|
||||||
|
public async Task<ActionResult<MobileLineDetailDto>> UploadAparelhoAnexos(
|
||||||
|
Guid id,
|
||||||
|
[FromForm] UploadAparelhoAnexosForm form)
|
||||||
|
{
|
||||||
|
var hasNotaFiscal = form.NotaFiscal != null && form.NotaFiscal.Length > 0;
|
||||||
|
var hasRecibo = form.Recibo != null && form.Recibo.Length > 0;
|
||||||
|
|
||||||
|
if (!hasNotaFiscal && !hasRecibo)
|
||||||
|
{
|
||||||
|
return BadRequest(new { message = "Envie ao menos um arquivo (Nota Fiscal ou Recibo)." });
|
||||||
|
}
|
||||||
|
|
||||||
|
var line = await _db.MobileLines
|
||||||
|
.Include(x => x.Aparelho)
|
||||||
|
.FirstOrDefaultAsync(x => x.Id == id);
|
||||||
|
if (line == null)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
var tenantId = line.TenantId != Guid.Empty
|
||||||
|
? line.TenantId
|
||||||
|
: (_tenantProvider.ActorTenantId ?? Guid.Empty);
|
||||||
|
if (tenantId == Guid.Empty)
|
||||||
|
{
|
||||||
|
return BadRequest(new { message = "Tenant inválido para salvar anexo." });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.TenantId == Guid.Empty)
|
||||||
|
{
|
||||||
|
line.TenantId = tenantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
var aparelho = await EnsureLineHasAparelhoAsync(line, tenantId);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (hasNotaFiscal && form.NotaFiscal != null)
|
||||||
|
{
|
||||||
|
aparelho.NotaFiscalArquivoPath = await SaveAparelhoAttachmentAsync(
|
||||||
|
form.NotaFiscal,
|
||||||
|
tenantId,
|
||||||
|
line.Id,
|
||||||
|
"nota-fiscal",
|
||||||
|
aparelho.NotaFiscalArquivoPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasRecibo && form.Recibo != null)
|
||||||
|
{
|
||||||
|
aparelho.ReciboArquivoPath = await SaveAparelhoAttachmentAsync(
|
||||||
|
form.Recibo,
|
||||||
|
tenantId,
|
||||||
|
line.Id,
|
||||||
|
"recibo",
|
||||||
|
aparelho.ReciboArquivoPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException ex)
|
||||||
|
{
|
||||||
|
return BadRequest(new { message = ex.Message });
|
||||||
|
}
|
||||||
|
catch (IOException)
|
||||||
|
{
|
||||||
|
return StatusCode(StatusCodes.Status500InternalServerError, new { message = "Falha ao salvar anexo no servidor." });
|
||||||
|
}
|
||||||
|
|
||||||
|
line.UpdatedAt = DateTime.UtcNow;
|
||||||
|
aparelho.UpdatedAt = DateTime.UtcNow;
|
||||||
|
|
||||||
|
await _db.SaveChangesAsync();
|
||||||
|
await _vigenciaNotificationSyncService.SyncCurrentTenantAsync();
|
||||||
|
|
||||||
|
var vigencia = await FindVigenciaByMobileLineAsync(line, null, asNoTracking: true);
|
||||||
|
return Ok(ToDetailDto(line, vigencia));
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{id:guid}/aparelho/anexos/{tipo}")]
|
||||||
|
[Authorize(Roles = "sysadmin,gestor,cliente")]
|
||||||
|
public async Task<IActionResult> DownloadAparelhoAnexo(Guid id, string tipo)
|
||||||
|
{
|
||||||
|
var line = await _db.MobileLines
|
||||||
|
.AsNoTracking()
|
||||||
|
.Include(x => x.Aparelho)
|
||||||
|
.FirstOrDefaultAsync(x => x.Id == id);
|
||||||
|
if (line == null || line.Aparelho == null)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
var normalizedTipo = (tipo ?? string.Empty).Trim().ToLowerInvariant();
|
||||||
|
var relativePath = normalizedTipo switch
|
||||||
|
{
|
||||||
|
"nota-fiscal" or "nota_fiscal" or "notafiscal" => line.Aparelho.NotaFiscalArquivoPath,
|
||||||
|
"recibo" => line.Aparelho.ReciboArquivoPath,
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(relativePath))
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
var fullPath = TryResolveAttachmentFullPath(relativePath);
|
||||||
|
if (string.IsNullOrWhiteSpace(fullPath) || !System.IO.File.Exists(fullPath))
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!FileContentTypeProvider.TryGetContentType(fullPath, out var contentType))
|
||||||
|
{
|
||||||
|
contentType = "application/octet-stream";
|
||||||
|
}
|
||||||
|
|
||||||
|
var downloadName = Path.GetFileName(fullPath);
|
||||||
|
var stream = new FileStream(fullPath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||||
|
return File(stream, contentType, downloadName);
|
||||||
|
}
|
||||||
|
|
||||||
// ==========================================================
|
// ==========================================================
|
||||||
// ✅ 7. DELETE
|
// ✅ 7. DELETE
|
||||||
// ==========================================================
|
// ==========================================================
|
||||||
|
|
@ -4098,6 +4302,7 @@ namespace line_gestao_api.Controllers
|
||||||
line.Linha = linhaLimpa;
|
line.Linha = linhaLimpa;
|
||||||
line.Chip = string.IsNullOrWhiteSpace(chipLimpo) ? null : chipLimpo;
|
line.Chip = string.IsNullOrWhiteSpace(chipLimpo) ? null : chipLimpo;
|
||||||
line.Usuario = req.Usuario?.Trim();
|
line.Usuario = req.Usuario?.Trim();
|
||||||
|
line.CentroDeCustos = NormalizeOptionalText(req.CentroDeCustos);
|
||||||
line.Status = req.Status?.Trim();
|
line.Status = req.Status?.Trim();
|
||||||
line.Skil = req.Skil?.Trim();
|
line.Skil = req.Skil?.Trim();
|
||||||
line.Modalidade = req.Modalidade?.Trim();
|
line.Modalidade = req.Modalidade?.Trim();
|
||||||
|
|
@ -4131,6 +4336,330 @@ namespace line_gestao_api.Controllers
|
||||||
line.UpdatedAt = now;
|
line.UpdatedAt = now;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task ApplySetorAndAparelhoToLineAsync(MobileLine line, CreateMobileLineDto req)
|
||||||
|
{
|
||||||
|
var tenantId = line.TenantId != Guid.Empty
|
||||||
|
? line.TenantId
|
||||||
|
: (_tenantProvider.ActorTenantId ?? Guid.Empty);
|
||||||
|
if (tenantId == Guid.Empty)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.TenantId == Guid.Empty)
|
||||||
|
{
|
||||||
|
line.TenantId = tenantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ApplySetorToLineAsync(line, tenantId, req.SetorId, req.SetorNome);
|
||||||
|
await ApplyAparelhoToLineAsync(
|
||||||
|
line,
|
||||||
|
tenantId,
|
||||||
|
req.AparelhoId,
|
||||||
|
req.AparelhoNome,
|
||||||
|
req.AparelhoCor,
|
||||||
|
req.AparelhoImei);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ApplySetorToLineAsync(MobileLine line, Guid tenantId, Guid? setorId, string? setorNome)
|
||||||
|
{
|
||||||
|
var hasSetorId = setorId.HasValue && setorId.Value != Guid.Empty;
|
||||||
|
var setorNomeInformado = setorNome != null;
|
||||||
|
var setorNomeNormalizado = NormalizeOptionalText(setorNome);
|
||||||
|
|
||||||
|
if (!hasSetorId && !setorNomeInformado)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasSetorId && setorNomeInformado && string.IsNullOrWhiteSpace(setorNomeNormalizado))
|
||||||
|
{
|
||||||
|
line.SetorId = null;
|
||||||
|
line.Setor = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var setor = await ResolveSetorAsync(tenantId, setorId, setorNomeNormalizado);
|
||||||
|
if (setor == null)
|
||||||
|
{
|
||||||
|
if (setorNomeInformado)
|
||||||
|
{
|
||||||
|
line.SetorId = null;
|
||||||
|
line.Setor = null;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
line.SetorId = setor.Id;
|
||||||
|
line.Setor = setor;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<Setor?> ResolveSetorAsync(Guid tenantId, Guid? setorId, string? setorNome)
|
||||||
|
{
|
||||||
|
if (setorId.HasValue && setorId.Value != Guid.Empty)
|
||||||
|
{
|
||||||
|
var byId = await _db.Setores.FirstOrDefaultAsync(s => s.TenantId == tenantId && s.Id == setorId.Value);
|
||||||
|
if (byId != null)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(setorNome) &&
|
||||||
|
!string.Equals(byId.Nome, setorNome, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
byId.Nome = setorNome;
|
||||||
|
byId.UpdatedAt = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
return byId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(setorNome))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var normalized = NormalizeTenantKeyValue(setorNome);
|
||||||
|
var candidates = await _db.Setores.Where(s => s.TenantId == tenantId).ToListAsync();
|
||||||
|
var existing = candidates.FirstOrDefault(s =>
|
||||||
|
string.Equals(NormalizeTenantKeyValue(s.Nome), normalized, StringComparison.Ordinal));
|
||||||
|
if (existing != null)
|
||||||
|
{
|
||||||
|
if (!string.Equals(existing.Nome, setorNome, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
existing.Nome = setorNome;
|
||||||
|
existing.UpdatedAt = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
var created = new Setor
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
TenantId = tenantId,
|
||||||
|
Nome = setorNome,
|
||||||
|
CreatedAt = now,
|
||||||
|
UpdatedAt = now
|
||||||
|
};
|
||||||
|
|
||||||
|
_db.Setores.Add(created);
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ApplyAparelhoToLineAsync(
|
||||||
|
MobileLine line,
|
||||||
|
Guid tenantId,
|
||||||
|
Guid? aparelhoId,
|
||||||
|
string? aparelhoNome,
|
||||||
|
string? aparelhoCor,
|
||||||
|
string? aparelhoImei)
|
||||||
|
{
|
||||||
|
var hasAparelhoId = aparelhoId.HasValue && aparelhoId.Value != Guid.Empty;
|
||||||
|
var hasAnyPayload =
|
||||||
|
aparelhoNome != null ||
|
||||||
|
aparelhoCor != null ||
|
||||||
|
aparelhoImei != null;
|
||||||
|
|
||||||
|
if (!hasAparelhoId && !hasAnyPayload)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var normalizedNome = NormalizeOptionalText(aparelhoNome);
|
||||||
|
var normalizedCor = NormalizeOptionalText(aparelhoCor);
|
||||||
|
var normalizedImei = NormalizeOptionalText(aparelhoImei);
|
||||||
|
|
||||||
|
var shouldDetach =
|
||||||
|
!hasAparelhoId &&
|
||||||
|
hasAnyPayload &&
|
||||||
|
string.IsNullOrWhiteSpace(normalizedNome) &&
|
||||||
|
string.IsNullOrWhiteSpace(normalizedCor) &&
|
||||||
|
string.IsNullOrWhiteSpace(normalizedImei);
|
||||||
|
|
||||||
|
if (shouldDetach)
|
||||||
|
{
|
||||||
|
line.AparelhoId = null;
|
||||||
|
line.Aparelho = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var aparelho = await ResolveAparelhoAsync(tenantId, aparelhoId, line.AparelhoId);
|
||||||
|
if (aparelho == null)
|
||||||
|
{
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
aparelho = new Aparelho
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
TenantId = tenantId,
|
||||||
|
CreatedAt = now,
|
||||||
|
UpdatedAt = now
|
||||||
|
};
|
||||||
|
_db.Aparelhos.Add(aparelho);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aparelhoNome != null) aparelho.Nome = normalizedNome;
|
||||||
|
if (aparelhoCor != null) aparelho.Cor = normalizedCor;
|
||||||
|
if (aparelhoImei != null) aparelho.Imei = normalizedImei;
|
||||||
|
|
||||||
|
aparelho.UpdatedAt = DateTime.UtcNow;
|
||||||
|
|
||||||
|
line.AparelhoId = aparelho.Id;
|
||||||
|
line.Aparelho = aparelho;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<Aparelho?> ResolveAparelhoAsync(Guid tenantId, Guid? aparelhoId, Guid? lineAparelhoId)
|
||||||
|
{
|
||||||
|
if (aparelhoId.HasValue && aparelhoId.Value != Guid.Empty)
|
||||||
|
{
|
||||||
|
var byRequestId = await _db.Aparelhos
|
||||||
|
.FirstOrDefaultAsync(a => a.TenantId == tenantId && a.Id == aparelhoId.Value);
|
||||||
|
if (byRequestId != null)
|
||||||
|
{
|
||||||
|
return byRequestId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lineAparelhoId.HasValue && lineAparelhoId.Value != Guid.Empty)
|
||||||
|
{
|
||||||
|
return await _db.Aparelhos
|
||||||
|
.FirstOrDefaultAsync(a => a.TenantId == tenantId && a.Id == lineAparelhoId.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<Aparelho> EnsureLineHasAparelhoAsync(MobileLine line, Guid tenantId)
|
||||||
|
{
|
||||||
|
if (line.Aparelho != null && line.Aparelho.TenantId == tenantId)
|
||||||
|
{
|
||||||
|
if (line.AparelhoId != line.Aparelho.Id)
|
||||||
|
{
|
||||||
|
line.AparelhoId = line.Aparelho.Id;
|
||||||
|
}
|
||||||
|
return line.Aparelho;
|
||||||
|
}
|
||||||
|
|
||||||
|
var aparelho = await ResolveAparelhoAsync(tenantId, line.AparelhoId, line.AparelhoId);
|
||||||
|
if (aparelho == null)
|
||||||
|
{
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
aparelho = new Aparelho
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
TenantId = tenantId,
|
||||||
|
CreatedAt = now,
|
||||||
|
UpdatedAt = now
|
||||||
|
};
|
||||||
|
_db.Aparelhos.Add(aparelho);
|
||||||
|
}
|
||||||
|
|
||||||
|
line.AparelhoId = aparelho.Id;
|
||||||
|
line.Aparelho = aparelho;
|
||||||
|
return aparelho;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<string> SaveAparelhoAttachmentAsync(
|
||||||
|
IFormFile file,
|
||||||
|
Guid tenantId,
|
||||||
|
Guid lineId,
|
||||||
|
string attachmentKind,
|
||||||
|
string? previousRelativePath)
|
||||||
|
{
|
||||||
|
const long maxBytes = 15 * 1024 * 1024;
|
||||||
|
if (file.Length <= 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Arquivo de anexo inválido.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.Length > maxBytes)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("O anexo excede o limite de 15MB.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var extension = (Path.GetExtension(file.FileName) ?? string.Empty).Trim().ToLowerInvariant();
|
||||||
|
var allowedExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
".pdf", ".png", ".jpg", ".jpeg", ".webp"
|
||||||
|
};
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(extension) || !allowedExtensions.Contains(extension))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Formato de arquivo inválido. Use PDF, PNG, JPG, JPEG ou WEBP.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetDirectory = Path.Combine(
|
||||||
|
_aparelhoAttachmentsRootPath,
|
||||||
|
tenantId.ToString("N"),
|
||||||
|
lineId.ToString("N"),
|
||||||
|
attachmentKind);
|
||||||
|
|
||||||
|
Directory.CreateDirectory(targetDirectory);
|
||||||
|
|
||||||
|
var safeName = $"{DateTime.UtcNow:yyyyMMddHHmmssfff}_{Guid.NewGuid():N}{extension}";
|
||||||
|
var fullPath = Path.Combine(targetDirectory, safeName);
|
||||||
|
|
||||||
|
await using (var stream = new FileStream(fullPath, FileMode.CreateNew, FileAccess.Write, FileShare.None))
|
||||||
|
{
|
||||||
|
await file.CopyToAsync(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(previousRelativePath))
|
||||||
|
{
|
||||||
|
var oldPath = TryResolveAttachmentFullPath(previousRelativePath);
|
||||||
|
if (!string.IsNullOrWhiteSpace(oldPath) &&
|
||||||
|
!string.Equals(oldPath, fullPath, StringComparison.OrdinalIgnoreCase) &&
|
||||||
|
System.IO.File.Exists(oldPath))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
System.IO.File.Delete(oldPath);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Falha ao remover arquivo antigo não deve impedir o fluxo principal.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var relativePath = Path.GetRelativePath(_aparelhoAttachmentsRootPath, fullPath);
|
||||||
|
return relativePath.Replace('\\', '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
private string? TryResolveAttachmentFullPath(string? relativePath)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(relativePath))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var normalized = relativePath
|
||||||
|
.Replace('\\', Path.DirectorySeparatorChar)
|
||||||
|
.TrimStart(Path.DirectorySeparatorChar, '/');
|
||||||
|
|
||||||
|
if (normalized.Contains("..", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var root = Path.GetFullPath(_aparelhoAttachmentsRootPath);
|
||||||
|
var fullPath = Path.GetFullPath(Path.Combine(root, normalized));
|
||||||
|
if (!fullPath.StartsWith(root, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fullPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? NormalizeOptionalText(string? value)
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var trimmed = value.Trim();
|
||||||
|
return trimmed.Length == 0 ? null : trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<Tenant?> EnsureTenantForClientAsync(string? rawClientName)
|
private async Task<Tenant?> EnsureTenantForClientAsync(string? rawClientName)
|
||||||
{
|
{
|
||||||
var clientName = (rawClientName ?? string.Empty).Trim();
|
var clientName = (rawClientName ?? string.Empty).Trim();
|
||||||
|
|
@ -4408,6 +4937,15 @@ namespace line_gestao_api.Controllers
|
||||||
Chip = x.Chip,
|
Chip = x.Chip,
|
||||||
Cliente = x.Cliente,
|
Cliente = x.Cliente,
|
||||||
Usuario = x.Usuario,
|
Usuario = x.Usuario,
|
||||||
|
CentroDeCustos = x.CentroDeCustos,
|
||||||
|
SetorId = x.SetorId,
|
||||||
|
SetorNome = x.Setor?.Nome,
|
||||||
|
AparelhoId = x.AparelhoId,
|
||||||
|
AparelhoNome = x.Aparelho?.Nome,
|
||||||
|
AparelhoCor = x.Aparelho?.Cor,
|
||||||
|
AparelhoImei = x.Aparelho?.Imei,
|
||||||
|
AparelhoNotaFiscalTemArquivo = !string.IsNullOrWhiteSpace(x.Aparelho?.NotaFiscalArquivoPath),
|
||||||
|
AparelhoReciboTemArquivo = !string.IsNullOrWhiteSpace(x.Aparelho?.ReciboArquivoPath),
|
||||||
PlanoContrato = x.PlanoContrato,
|
PlanoContrato = x.PlanoContrato,
|
||||||
FranquiaVivo = x.FranquiaVivo,
|
FranquiaVivo = x.FranquiaVivo,
|
||||||
ValorPlanoVivo = x.ValorPlanoVivo,
|
ValorPlanoVivo = x.ValorPlanoVivo,
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@ public class AppDbContext : IdentityDbContext<ApplicationUser, IdentityRole<Guid
|
||||||
|
|
||||||
// ✅ tabela para espelhar a planilha (GERAL)
|
// ✅ tabela para espelhar a planilha (GERAL)
|
||||||
public DbSet<MobileLine> MobileLines => Set<MobileLine>();
|
public DbSet<MobileLine> MobileLines => Set<MobileLine>();
|
||||||
|
public DbSet<Setor> Setores => Set<Setor>();
|
||||||
|
public DbSet<Aparelho> Aparelhos => Set<Aparelho>();
|
||||||
|
|
||||||
// ✅ tabela para espelhar a aba MUREG
|
// ✅ tabela para espelhar a aba MUREG
|
||||||
public DbSet<MuregLine> MuregLines => Set<MuregLine>();
|
public DbSet<MuregLine> MuregLines => Set<MuregLine>();
|
||||||
|
|
@ -87,6 +89,25 @@ public class AppDbContext : IdentityDbContext<ApplicationUser, IdentityRole<Guid
|
||||||
e.HasIndex(x => new { x.IsSystem, x.Ativo });
|
e.HasIndex(x => new { x.IsSystem, x.Ativo });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity<Setor>(e =>
|
||||||
|
{
|
||||||
|
e.Property(x => x.Nome).HasMaxLength(160);
|
||||||
|
e.HasIndex(x => x.TenantId);
|
||||||
|
e.HasIndex(x => new { x.TenantId, x.Nome }).IsUnique();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity<Aparelho>(e =>
|
||||||
|
{
|
||||||
|
e.Property(x => x.Nome).HasMaxLength(160);
|
||||||
|
e.Property(x => x.Cor).HasMaxLength(80);
|
||||||
|
e.Property(x => x.Imei).HasMaxLength(80);
|
||||||
|
e.Property(x => x.NotaFiscalArquivoPath).HasMaxLength(500);
|
||||||
|
e.Property(x => x.ReciboArquivoPath).HasMaxLength(500);
|
||||||
|
e.HasIndex(x => x.TenantId);
|
||||||
|
e.HasIndex(x => x.Imei);
|
||||||
|
e.HasIndex(x => new { x.TenantId, x.Nome, x.Cor });
|
||||||
|
});
|
||||||
|
|
||||||
// =========================
|
// =========================
|
||||||
// ✅ USER (Identity)
|
// ✅ USER (Identity)
|
||||||
// =========================
|
// =========================
|
||||||
|
|
@ -104,13 +125,27 @@ public class AppDbContext : IdentityDbContext<ApplicationUser, IdentityRole<Guid
|
||||||
{
|
{
|
||||||
// Mantém UNIQUE por Linha por tenant (se Linha puder ser null no banco, Postgres aceita múltiplos nulls)
|
// Mantém UNIQUE por Linha por tenant (se Linha puder ser null no banco, Postgres aceita múltiplos nulls)
|
||||||
e.HasIndex(x => new { x.TenantId, x.Linha }).IsUnique();
|
e.HasIndex(x => new { x.TenantId, x.Linha }).IsUnique();
|
||||||
|
e.Property(x => x.CentroDeCustos).HasMaxLength(180);
|
||||||
|
|
||||||
// performance
|
// performance
|
||||||
e.HasIndex(x => x.Chip);
|
e.HasIndex(x => x.Chip);
|
||||||
e.HasIndex(x => x.Cliente);
|
e.HasIndex(x => x.Cliente);
|
||||||
e.HasIndex(x => x.Usuario);
|
e.HasIndex(x => x.Usuario);
|
||||||
|
e.HasIndex(x => x.CentroDeCustos);
|
||||||
e.HasIndex(x => x.Skil);
|
e.HasIndex(x => x.Skil);
|
||||||
e.HasIndex(x => x.Status);
|
e.HasIndex(x => x.Status);
|
||||||
|
e.HasIndex(x => x.SetorId);
|
||||||
|
e.HasIndex(x => x.AparelhoId);
|
||||||
|
|
||||||
|
e.HasOne(x => x.Setor)
|
||||||
|
.WithMany(x => x.MobileLines)
|
||||||
|
.HasForeignKey(x => x.SetorId)
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
e.HasOne(x => x.Aparelho)
|
||||||
|
.WithMany(x => x.MobileLines)
|
||||||
|
.HasForeignKey(x => x.AparelhoId)
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
});
|
});
|
||||||
|
|
||||||
// =========================
|
// =========================
|
||||||
|
|
@ -336,6 +371,8 @@ public class AppDbContext : IdentityDbContext<ApplicationUser, IdentityRole<Guid
|
||||||
|
|
||||||
modelBuilder.Entity<MobileLine>().HasQueryFilter(x => _tenantProvider.HasGlobalViewAccess || (_tenantProvider.ActorTenantId != null && x.TenantId == _tenantProvider.ActorTenantId));
|
modelBuilder.Entity<MobileLine>().HasQueryFilter(x => _tenantProvider.HasGlobalViewAccess || (_tenantProvider.ActorTenantId != null && x.TenantId == _tenantProvider.ActorTenantId));
|
||||||
modelBuilder.Entity<MuregLine>().HasQueryFilter(x => _tenantProvider.HasGlobalViewAccess || (_tenantProvider.ActorTenantId != null && x.TenantId == _tenantProvider.ActorTenantId));
|
modelBuilder.Entity<MuregLine>().HasQueryFilter(x => _tenantProvider.HasGlobalViewAccess || (_tenantProvider.ActorTenantId != null && x.TenantId == _tenantProvider.ActorTenantId));
|
||||||
|
modelBuilder.Entity<Setor>().HasQueryFilter(x => _tenantProvider.HasGlobalViewAccess || (_tenantProvider.ActorTenantId != null && x.TenantId == _tenantProvider.ActorTenantId));
|
||||||
|
modelBuilder.Entity<Aparelho>().HasQueryFilter(x => _tenantProvider.HasGlobalViewAccess || (_tenantProvider.ActorTenantId != null && x.TenantId == _tenantProvider.ActorTenantId));
|
||||||
modelBuilder.Entity<BillingClient>().HasQueryFilter(x => _tenantProvider.HasGlobalViewAccess || (_tenantProvider.ActorTenantId != null && x.TenantId == _tenantProvider.ActorTenantId));
|
modelBuilder.Entity<BillingClient>().HasQueryFilter(x => _tenantProvider.HasGlobalViewAccess || (_tenantProvider.ActorTenantId != null && x.TenantId == _tenantProvider.ActorTenantId));
|
||||||
modelBuilder.Entity<UserData>().HasQueryFilter(x => _tenantProvider.HasGlobalViewAccess || (_tenantProvider.ActorTenantId != null && x.TenantId == _tenantProvider.ActorTenantId));
|
modelBuilder.Entity<UserData>().HasQueryFilter(x => _tenantProvider.HasGlobalViewAccess || (_tenantProvider.ActorTenantId != null && x.TenantId == _tenantProvider.ActorTenantId));
|
||||||
modelBuilder.Entity<VigenciaLine>().HasQueryFilter(x => _tenantProvider.HasGlobalViewAccess || (_tenantProvider.ActorTenantId != null && x.TenantId == _tenantProvider.ActorTenantId));
|
modelBuilder.Entity<VigenciaLine>().HasQueryFilter(x => _tenantProvider.HasGlobalViewAccess || (_tenantProvider.ActorTenantId != null && x.TenantId == _tenantProvider.ActorTenantId));
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,13 @@ namespace line_gestao_api.Dtos
|
||||||
public string? Chip { get; set; } // ICCID
|
public string? Chip { get; set; } // ICCID
|
||||||
public string? Cliente { get; set; } // Obrigatório na validação do Controller
|
public string? Cliente { get; set; } // Obrigatório na validação do Controller
|
||||||
public string? Usuario { get; set; }
|
public string? Usuario { get; set; }
|
||||||
|
public string? CentroDeCustos { get; set; }
|
||||||
|
public Guid? SetorId { get; set; }
|
||||||
|
public string? SetorNome { get; set; }
|
||||||
|
public Guid? AparelhoId { get; set; }
|
||||||
|
public string? AparelhoNome { get; set; }
|
||||||
|
public string? AparelhoCor { get; set; }
|
||||||
|
public string? AparelhoImei { get; set; }
|
||||||
public Guid? ReservaLineId { get; set; } // Reaproveita linha já existente na Reserva
|
public Guid? ReservaLineId { get; set; } // Reaproveita linha já existente na Reserva
|
||||||
|
|
||||||
// ==========================
|
// ==========================
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,10 @@
|
||||||
public string? Chip { get; set; }
|
public string? Chip { get; set; }
|
||||||
public string? Cliente { get; set; }
|
public string? Cliente { get; set; }
|
||||||
public string? Usuario { get; set; }
|
public string? Usuario { get; set; }
|
||||||
|
public string? CentroDeCustos { get; set; }
|
||||||
|
public string? SetorNome { get; set; }
|
||||||
|
public string? AparelhoNome { get; set; }
|
||||||
|
public string? AparelhoCor { get; set; }
|
||||||
public string? PlanoContrato { get; set; }
|
public string? PlanoContrato { get; set; }
|
||||||
public string? Status { get; set; }
|
public string? Status { get; set; }
|
||||||
public string? Skil { get; set; }
|
public string? Skil { get; set; }
|
||||||
|
|
@ -35,6 +39,15 @@
|
||||||
public string? Chip { get; set; }
|
public string? Chip { get; set; }
|
||||||
public string? Cliente { get; set; }
|
public string? Cliente { get; set; }
|
||||||
public string? Usuario { get; set; }
|
public string? Usuario { get; set; }
|
||||||
|
public string? CentroDeCustos { get; set; }
|
||||||
|
public Guid? SetorId { get; set; }
|
||||||
|
public string? SetorNome { get; set; }
|
||||||
|
public Guid? AparelhoId { get; set; }
|
||||||
|
public string? AparelhoNome { get; set; }
|
||||||
|
public string? AparelhoCor { get; set; }
|
||||||
|
public string? AparelhoImei { get; set; }
|
||||||
|
public bool AparelhoNotaFiscalTemArquivo { get; set; }
|
||||||
|
public bool AparelhoReciboTemArquivo { get; set; }
|
||||||
public string? PlanoContrato { get; set; }
|
public string? PlanoContrato { get; set; }
|
||||||
|
|
||||||
public decimal? FranquiaVivo { get; set; }
|
public decimal? FranquiaVivo { get; set; }
|
||||||
|
|
@ -78,6 +91,13 @@
|
||||||
public string? Chip { get; set; }
|
public string? Chip { get; set; }
|
||||||
public string? Cliente { get; set; }
|
public string? Cliente { get; set; }
|
||||||
public string? Usuario { get; set; }
|
public string? Usuario { get; set; }
|
||||||
|
public string? CentroDeCustos { get; set; }
|
||||||
|
public Guid? SetorId { get; set; }
|
||||||
|
public string? SetorNome { get; set; }
|
||||||
|
public Guid? AparelhoId { get; set; }
|
||||||
|
public string? AparelhoNome { get; set; }
|
||||||
|
public string? AparelhoCor { get; set; }
|
||||||
|
public string? AparelhoImei { get; set; }
|
||||||
public string? PlanoContrato { get; set; }
|
public string? PlanoContrato { get; set; }
|
||||||
|
|
||||||
public decimal? FranquiaVivo { get; set; }
|
public decimal? FranquiaVivo { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,114 @@
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using line_gestao_api.Data;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace line_gestao_api.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(AppDbContext))]
|
||||||
|
[Migration("20260303120000_AddSetoresAparelhosAndMobileLineCostCenter")]
|
||||||
|
public partial class AddSetoresAparelhosAndMobileLineCostCenter : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.Sql("""
|
||||||
|
CREATE TABLE IF NOT EXISTS "Setores" (
|
||||||
|
"Id" uuid NOT NULL,
|
||||||
|
"TenantId" uuid NOT NULL,
|
||||||
|
"Nome" character varying(160) NOT NULL,
|
||||||
|
"CreatedAt" timestamp with time zone NOT NULL,
|
||||||
|
"UpdatedAt" timestamp with time zone NOT NULL,
|
||||||
|
CONSTRAINT "PK_Setores" PRIMARY KEY ("Id")
|
||||||
|
);
|
||||||
|
""");
|
||||||
|
|
||||||
|
migrationBuilder.Sql("""
|
||||||
|
CREATE TABLE IF NOT EXISTS "Aparelhos" (
|
||||||
|
"Id" uuid NOT NULL,
|
||||||
|
"TenantId" uuid NOT NULL,
|
||||||
|
"Nome" character varying(160) NULL,
|
||||||
|
"Cor" character varying(80) NULL,
|
||||||
|
"Imei" character varying(80) NULL,
|
||||||
|
"NotaFiscalArquivoPath" character varying(500) NULL,
|
||||||
|
"ReciboArquivoPath" character varying(500) NULL,
|
||||||
|
"CreatedAt" timestamp with time zone NOT NULL,
|
||||||
|
"UpdatedAt" timestamp with time zone NOT NULL,
|
||||||
|
CONSTRAINT "PK_Aparelhos" PRIMARY KEY ("Id")
|
||||||
|
);
|
||||||
|
""");
|
||||||
|
|
||||||
|
migrationBuilder.Sql("""ALTER TABLE "MobileLines" ADD COLUMN IF NOT EXISTS "CentroDeCustos" character varying(180) NULL;""");
|
||||||
|
migrationBuilder.Sql("""ALTER TABLE "MobileLines" ADD COLUMN IF NOT EXISTS "SetorId" uuid NULL;""");
|
||||||
|
migrationBuilder.Sql("""ALTER TABLE "MobileLines" ADD COLUMN IF NOT EXISTS "AparelhoId" uuid NULL;""");
|
||||||
|
|
||||||
|
migrationBuilder.Sql("""CREATE INDEX IF NOT EXISTS "IX_Setores_TenantId" ON "Setores" ("TenantId");""");
|
||||||
|
migrationBuilder.Sql("""CREATE UNIQUE INDEX IF NOT EXISTS "IX_Setores_TenantId_Nome" ON "Setores" ("TenantId", "Nome");""");
|
||||||
|
migrationBuilder.Sql("""CREATE INDEX IF NOT EXISTS "IX_Aparelhos_TenantId" ON "Aparelhos" ("TenantId");""");
|
||||||
|
migrationBuilder.Sql("""CREATE INDEX IF NOT EXISTS "IX_Aparelhos_Imei" ON "Aparelhos" ("Imei");""");
|
||||||
|
migrationBuilder.Sql("""CREATE INDEX IF NOT EXISTS "IX_Aparelhos_TenantId_Nome_Cor" ON "Aparelhos" ("TenantId", "Nome", "Cor");""");
|
||||||
|
migrationBuilder.Sql("""CREATE INDEX IF NOT EXISTS "IX_MobileLines_CentroDeCustos" ON "MobileLines" ("CentroDeCustos");""");
|
||||||
|
migrationBuilder.Sql("""CREATE INDEX IF NOT EXISTS "IX_MobileLines_SetorId" ON "MobileLines" ("SetorId");""");
|
||||||
|
migrationBuilder.Sql("""CREATE INDEX IF NOT EXISTS "IX_MobileLines_AparelhoId" ON "MobileLines" ("AparelhoId");""");
|
||||||
|
|
||||||
|
migrationBuilder.Sql("""
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM information_schema.table_constraints
|
||||||
|
WHERE constraint_name = 'FK_MobileLines_Setores_SetorId'
|
||||||
|
AND table_name = 'MobileLines'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE "MobileLines"
|
||||||
|
ADD CONSTRAINT "FK_MobileLines_Setores_SetorId"
|
||||||
|
FOREIGN KEY ("SetorId") REFERENCES "Setores" ("Id")
|
||||||
|
ON DELETE SET NULL;
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$$;
|
||||||
|
""");
|
||||||
|
|
||||||
|
migrationBuilder.Sql("""
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM information_schema.table_constraints
|
||||||
|
WHERE constraint_name = 'FK_MobileLines_Aparelhos_AparelhoId'
|
||||||
|
AND table_name = 'MobileLines'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE "MobileLines"
|
||||||
|
ADD CONSTRAINT "FK_MobileLines_Aparelhos_AparelhoId"
|
||||||
|
FOREIGN KEY ("AparelhoId") REFERENCES "Aparelhos" ("Id")
|
||||||
|
ON DELETE SET NULL;
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$$;
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.Sql("""ALTER TABLE "MobileLines" DROP CONSTRAINT IF EXISTS "FK_MobileLines_Setores_SetorId";""");
|
||||||
|
migrationBuilder.Sql("""ALTER TABLE "MobileLines" DROP CONSTRAINT IF EXISTS "FK_MobileLines_Aparelhos_AparelhoId";""");
|
||||||
|
|
||||||
|
migrationBuilder.Sql("""DROP INDEX IF EXISTS "IX_MobileLines_AparelhoId";""");
|
||||||
|
migrationBuilder.Sql("""DROP INDEX IF EXISTS "IX_MobileLines_SetorId";""");
|
||||||
|
migrationBuilder.Sql("""DROP INDEX IF EXISTS "IX_MobileLines_CentroDeCustos";""");
|
||||||
|
|
||||||
|
migrationBuilder.Sql("""ALTER TABLE "MobileLines" DROP COLUMN IF EXISTS "AparelhoId";""");
|
||||||
|
migrationBuilder.Sql("""ALTER TABLE "MobileLines" DROP COLUMN IF EXISTS "SetorId";""");
|
||||||
|
migrationBuilder.Sql("""ALTER TABLE "MobileLines" DROP COLUMN IF EXISTS "CentroDeCustos";""");
|
||||||
|
|
||||||
|
migrationBuilder.Sql("""DROP INDEX IF EXISTS "IX_Aparelhos_TenantId_Nome_Cor";""");
|
||||||
|
migrationBuilder.Sql("""DROP INDEX IF EXISTS "IX_Aparelhos_Imei";""");
|
||||||
|
migrationBuilder.Sql("""DROP INDEX IF EXISTS "IX_Aparelhos_TenantId";""");
|
||||||
|
migrationBuilder.Sql("""DROP INDEX IF EXISTS "IX_Setores_TenantId_Nome";""");
|
||||||
|
migrationBuilder.Sql("""DROP INDEX IF EXISTS "IX_Setores_TenantId";""");
|
||||||
|
|
||||||
|
migrationBuilder.Sql("""DROP TABLE IF EXISTS "Aparelhos";""");
|
||||||
|
migrationBuilder.Sql("""DROP TABLE IF EXISTS "Setores";""");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using line_gestao_api.Data;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace line_gestao_api.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(AppDbContext))]
|
||||||
|
[Migration("20260303193000_FixAparelhosArquivoPathColumns")]
|
||||||
|
public partial class FixAparelhosArquivoPathColumns : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.Sql("""
|
||||||
|
ALTER TABLE "Aparelhos"
|
||||||
|
ADD COLUMN IF NOT EXISTS "NotaFiscalArquivoPath" character varying(500) NULL;
|
||||||
|
""");
|
||||||
|
|
||||||
|
migrationBuilder.Sql("""
|
||||||
|
ALTER TABLE "Aparelhos"
|
||||||
|
ADD COLUMN IF NOT EXISTS "ReciboArquivoPath" character varying(500) NULL;
|
||||||
|
""");
|
||||||
|
|
||||||
|
// Backfill seguro para bancos que já tinham os campos antigos de URL/nome.
|
||||||
|
migrationBuilder.Sql("""
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_name = 'Aparelhos'
|
||||||
|
AND column_name = 'NotaFiscalAnexoUrl'
|
||||||
|
) THEN
|
||||||
|
UPDATE "Aparelhos"
|
||||||
|
SET "NotaFiscalArquivoPath" = COALESCE("NotaFiscalArquivoPath", "NotaFiscalAnexoUrl")
|
||||||
|
WHERE "NotaFiscalAnexoUrl" IS NOT NULL
|
||||||
|
AND "NotaFiscalAnexoUrl" <> '';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_name = 'Aparelhos'
|
||||||
|
AND column_name = 'ReciboAnexoUrl'
|
||||||
|
) THEN
|
||||||
|
UPDATE "Aparelhos"
|
||||||
|
SET "ReciboArquivoPath" = COALESCE("ReciboArquivoPath", "ReciboAnexoUrl")
|
||||||
|
WHERE "ReciboAnexoUrl" IS NOT NULL
|
||||||
|
AND "ReciboAnexoUrl" <> '';
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$$;
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.Sql("""ALTER TABLE "Aparelhos" DROP COLUMN IF EXISTS "ReciboArquivoPath";""");
|
||||||
|
migrationBuilder.Sql("""ALTER TABLE "Aparelhos" DROP COLUMN IF EXISTS "NotaFiscalArquivoPath";""");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -330,6 +330,52 @@ namespace line_gestao_api.Migrations
|
||||||
b.ToTable("AuditLogs");
|
b.ToTable("AuditLogs");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("line_gestao_api.Models.Aparelho", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("Cor")
|
||||||
|
.HasMaxLength(80)
|
||||||
|
.HasColumnType("character varying(80)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Imei")
|
||||||
|
.HasMaxLength(80)
|
||||||
|
.HasColumnType("character varying(80)");
|
||||||
|
|
||||||
|
b.Property<string>("Nome")
|
||||||
|
.HasMaxLength(160)
|
||||||
|
.HasColumnType("character varying(160)");
|
||||||
|
|
||||||
|
b.Property<string>("NotaFiscalArquivoPath")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("character varying(500)");
|
||||||
|
|
||||||
|
b.Property<string>("ReciboArquivoPath")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("character varying(500)");
|
||||||
|
|
||||||
|
b.Property<Guid>("TenantId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Imei");
|
||||||
|
|
||||||
|
b.HasIndex("TenantId");
|
||||||
|
|
||||||
|
b.HasIndex("TenantId", "Nome", "Cor");
|
||||||
|
|
||||||
|
b.ToTable("Aparelhos");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("line_gestao_api.Models.BillingClient", b =>
|
modelBuilder.Entity("line_gestao_api.Models.BillingClient", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
|
|
@ -624,10 +670,17 @@ namespace line_gestao_api.Migrations
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("uuid");
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid?>("AparelhoId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
b.Property<string>("Cedente")
|
b.Property<string>("Cedente")
|
||||||
.HasMaxLength(150)
|
.HasMaxLength(150)
|
||||||
.HasColumnType("character varying(150)");
|
.HasColumnType("character varying(150)");
|
||||||
|
|
||||||
|
b.Property<string>("CentroDeCustos")
|
||||||
|
.HasMaxLength(180)
|
||||||
|
.HasColumnType("character varying(180)");
|
||||||
|
|
||||||
b.Property<string>("Chip")
|
b.Property<string>("Chip")
|
||||||
.HasMaxLength(40)
|
.HasMaxLength(40)
|
||||||
.HasColumnType("character varying(40)");
|
.HasColumnType("character varying(40)");
|
||||||
|
|
@ -691,6 +744,9 @@ namespace line_gestao_api.Migrations
|
||||||
b.Property<decimal?>("Skeelo")
|
b.Property<decimal?>("Skeelo")
|
||||||
.HasColumnType("numeric");
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<Guid?>("SetorId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
b.Property<string>("Skil")
|
b.Property<string>("Skil")
|
||||||
.HasMaxLength(80)
|
.HasMaxLength(80)
|
||||||
.HasColumnType("character varying(80)");
|
.HasColumnType("character varying(80)");
|
||||||
|
|
@ -744,10 +800,16 @@ namespace line_gestao_api.Migrations
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AparelhoId");
|
||||||
|
|
||||||
b.HasIndex("Chip");
|
b.HasIndex("Chip");
|
||||||
|
|
||||||
|
b.HasIndex("CentroDeCustos");
|
||||||
|
|
||||||
b.HasIndex("Cliente");
|
b.HasIndex("Cliente");
|
||||||
|
|
||||||
|
b.HasIndex("SetorId");
|
||||||
|
|
||||||
b.HasIndex("Skil");
|
b.HasIndex("Skil");
|
||||||
|
|
||||||
b.HasIndex("Status");
|
b.HasIndex("Status");
|
||||||
|
|
@ -1370,6 +1432,36 @@ namespace line_gestao_api.Migrations
|
||||||
b.ToTable("ResumoVivoLineTotals");
|
b.ToTable("ResumoVivoLineTotals");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("line_gestao_api.Models.Setor", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Nome")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(160)
|
||||||
|
.HasColumnType("character varying(160)");
|
||||||
|
|
||||||
|
b.Property<Guid>("TenantId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("TenantId");
|
||||||
|
|
||||||
|
b.HasIndex("TenantId", "Nome")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("Setores");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("line_gestao_api.Models.Tenant", b =>
|
modelBuilder.Entity("line_gestao_api.Models.Tenant", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
|
|
@ -1665,6 +1757,23 @@ namespace line_gestao_api.Migrations
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("line_gestao_api.Models.MobileLine", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("line_gestao_api.Models.Aparelho", "Aparelho")
|
||||||
|
.WithMany("MobileLines")
|
||||||
|
.HasForeignKey("AparelhoId")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
b.HasOne("line_gestao_api.Models.Setor", "Setor")
|
||||||
|
.WithMany("MobileLines")
|
||||||
|
.HasForeignKey("SetorId")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
b.Navigation("Aparelho");
|
||||||
|
|
||||||
|
b.Navigation("Setor");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("line_gestao_api.Models.MuregLine", b =>
|
modelBuilder.Entity("line_gestao_api.Models.MuregLine", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("line_gestao_api.Models.MobileLine", "MobileLine")
|
b.HasOne("line_gestao_api.Models.MobileLine", "MobileLine")
|
||||||
|
|
@ -1715,6 +1824,11 @@ namespace line_gestao_api.Migrations
|
||||||
b.Navigation("ParcelamentoLine");
|
b.Navigation("ParcelamentoLine");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("line_gestao_api.Models.Aparelho", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("MobileLines");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("line_gestao_api.Models.MobileLine", b =>
|
modelBuilder.Entity("line_gestao_api.Models.MobileLine", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("Muregs");
|
b.Navigation("Muregs");
|
||||||
|
|
@ -1729,6 +1843,11 @@ namespace line_gestao_api.Migrations
|
||||||
{
|
{
|
||||||
b.Navigation("MonthValues");
|
b.Navigation("MonthValues");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("line_gestao_api.Models.Setor", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("MobileLines");
|
||||||
|
});
|
||||||
#pragma warning restore 612, 618
|
#pragma warning restore 612, 618
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace line_gestao_api.Models;
|
||||||
|
|
||||||
|
public class Aparelho : ITenantEntity
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
|
|
||||||
|
public Guid TenantId { get; set; }
|
||||||
|
|
||||||
|
[MaxLength(160)]
|
||||||
|
public string? Nome { get; set; }
|
||||||
|
|
||||||
|
[MaxLength(80)]
|
||||||
|
public string? Cor { get; set; }
|
||||||
|
|
||||||
|
[MaxLength(80)]
|
||||||
|
public string? Imei { get; set; }
|
||||||
|
|
||||||
|
[MaxLength(500)]
|
||||||
|
public string? NotaFiscalArquivoPath { get; set; }
|
||||||
|
|
||||||
|
[MaxLength(500)]
|
||||||
|
public string? ReciboArquivoPath { get; set; }
|
||||||
|
|
||||||
|
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
|
||||||
|
public ICollection<MobileLine> MobileLines { get; set; } = new List<MobileLine>();
|
||||||
|
}
|
||||||
|
|
@ -20,6 +20,15 @@ namespace line_gestao_api.Models
|
||||||
public string? Cliente { get; set; }
|
public string? Cliente { get; set; }
|
||||||
[MaxLength(200)]
|
[MaxLength(200)]
|
||||||
public string? Usuario { get; set; }
|
public string? Usuario { get; set; }
|
||||||
|
[MaxLength(180)]
|
||||||
|
public string? CentroDeCustos { get; set; }
|
||||||
|
|
||||||
|
public Guid? SetorId { get; set; }
|
||||||
|
public Setor? Setor { get; set; }
|
||||||
|
|
||||||
|
public Guid? AparelhoId { get; set; }
|
||||||
|
public Aparelho? Aparelho { get; set; }
|
||||||
|
|
||||||
[MaxLength(200)]
|
[MaxLength(200)]
|
||||||
public string? PlanoContrato { get; set; }
|
public string? PlanoContrato { get; set; }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace line_gestao_api.Models;
|
||||||
|
|
||||||
|
public class Setor : ITenantEntity
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
|
|
||||||
|
public Guid TenantId { get; set; }
|
||||||
|
|
||||||
|
[MaxLength(160)]
|
||||||
|
public string Nome { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
|
||||||
|
public ICollection<MobileLine> MobileLines { get; set; } = new List<MobileLine>();
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue