Finalización de Reportes y arreglos varios de controles y comportamientos...
This commit is contained in:
@@ -83,7 +83,10 @@ namespace GestionIntegral.Api.Controllers.Distribucion
|
|||||||
public async Task<IActionResult> UpdateMovimiento(int idParte, [FromBody] UpdateEntradaSalidaCanillaDto updateDto)
|
public async Task<IActionResult> UpdateMovimiento(int idParte, [FromBody] UpdateEntradaSalidaCanillaDto updateDto)
|
||||||
{
|
{
|
||||||
if (!TienePermiso(PermisoModificarMovimiento)) return Forbid();
|
if (!TienePermiso(PermisoModificarMovimiento)) return Forbid();
|
||||||
|
|
||||||
|
// Esta línea es la que dispara la validación del modelo 'updateDto'
|
||||||
if (!ModelState.IsValid) return BadRequest(ModelState);
|
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||||
|
|
||||||
var userId = GetCurrentUserId();
|
var userId = GetCurrentUserId();
|
||||||
if (userId == null) return Unauthorized();
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
@@ -91,7 +94,7 @@ namespace GestionIntegral.Api.Controllers.Distribucion
|
|||||||
if (!exito)
|
if (!exito)
|
||||||
{
|
{
|
||||||
if (error == "Movimiento no encontrado." || error == "No se puede modificar un movimiento ya liquidado.")
|
if (error == "Movimiento no encontrado." || error == "No se puede modificar un movimiento ya liquidado.")
|
||||||
return NotFound(new { message = error }); // Podría ser 404 o 400 dependiendo del error
|
return NotFound(new { message = error });
|
||||||
return BadRequest(new { message = error });
|
return BadRequest(new { message = error });
|
||||||
}
|
}
|
||||||
return NoContent();
|
return NoContent();
|
||||||
|
|||||||
@@ -117,5 +117,65 @@ namespace GestionIntegral.Api.Controllers.Distribucion
|
|||||||
}
|
}
|
||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Endpoint para obtener las configuraciones de días para una publicación
|
||||||
|
[HttpGet("{idPublicacion:int}/dias-semana")]
|
||||||
|
[ProducesResponseType(typeof(IEnumerable<PublicacionDiaSemanaDto>), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> GetConfiguracionDiasPublicacion(int idPublicacion)
|
||||||
|
{
|
||||||
|
// Podrías usar el mismo permiso de ver publicaciones o uno específico
|
||||||
|
if (!TienePermiso(PermisoVer)) return Forbid();
|
||||||
|
|
||||||
|
var publicacion = await _publicacionService.ObtenerPorIdAsync(idPublicacion);
|
||||||
|
if (publicacion == null) return NotFound(new { message = "Publicación no encontrada." });
|
||||||
|
|
||||||
|
var configs = await _publicacionService.ObtenerConfiguracionDiasAsync(idPublicacion);
|
||||||
|
return Ok(configs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Endpoint para actualizar las configuraciones de días para una publicación
|
||||||
|
[HttpPut("{idPublicacion:int}/dias-semana")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> UpdateConfiguracionDiasPublicacion(int idPublicacion, [FromBody] UpdatePublicacionDiasSemanaRequestDto requestDto)
|
||||||
|
{
|
||||||
|
// Podrías usar el mismo permiso de modificar publicaciones o uno específico
|
||||||
|
if (!TienePermiso(PermisoModificar)) return Forbid();
|
||||||
|
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||||
|
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var (exito, error) = await _publicacionService.ActualizarConfiguracionDiasAsync(idPublicacion, requestDto, userId.Value);
|
||||||
|
if (!exito)
|
||||||
|
{
|
||||||
|
if (error == "Publicación no encontrada.") return NotFound(new { message = error });
|
||||||
|
return BadRequest(new { message = error });
|
||||||
|
}
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Endpoint para obtener publicaciones por día de la semana (para el modal de canillitas)
|
||||||
|
[HttpGet("por-dia-semana")]
|
||||||
|
[ProducesResponseType(typeof(IEnumerable<PublicacionDto>), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
public async Task<IActionResult> GetPublicacionesPorDia([FromQuery] byte dia) // dia: 0=Domingo, 1=Lunes...
|
||||||
|
{
|
||||||
|
// Generalmente, este endpoint no necesitaría un permiso estricto si solo devuelve datos públicos
|
||||||
|
// pero puedes añadirlo si es necesario.
|
||||||
|
// if (!TienePermiso(PermisoVer)) return Forbid();
|
||||||
|
|
||||||
|
if (dia > 6) // byte no puede ser negativo
|
||||||
|
{
|
||||||
|
return BadRequest(new { message = "El día de la semana debe estar entre 0 (Domingo) y 6 (Sábado)." });
|
||||||
|
}
|
||||||
|
var publicaciones = await _publicacionService.ObtenerPublicacionesPorDiaSemanaAsync(dia);
|
||||||
|
return Ok(publicaciones);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,992 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Report xmlns="http://schemas.microsoft.com/sqlserver/reporting/2016/01/reportdefinition" xmlns:rd="http://schemas.microsoft.com/SQLServer/reporting/reportdesigner">
|
||||||
|
<AutoRefresh>0</AutoRefresh>
|
||||||
|
<DataSources>
|
||||||
|
<DataSource Name="DSLiquidacionCanillas">
|
||||||
|
<ConnectionProperties>
|
||||||
|
<DataProvider>System.Data.DataSet</DataProvider>
|
||||||
|
<ConnectString>/* Local Connection */</ConnectString>
|
||||||
|
</ConnectionProperties>
|
||||||
|
<rd:DataSourceID>ee328c50-edc3-4726-92dc-19ec72ac5a56</rd:DataSourceID>
|
||||||
|
</DataSource>
|
||||||
|
</DataSources>
|
||||||
|
<DataSets>
|
||||||
|
<DataSet Name="DSLiquidacionCanillas">
|
||||||
|
<Query>
|
||||||
|
<DataSourceName>DSLiquidacionCanillas</DataSourceName>
|
||||||
|
<CommandText>/* Local Query */</CommandText>
|
||||||
|
</Query>
|
||||||
|
<Fields>
|
||||||
|
<Field Name="Publicacion">
|
||||||
|
<DataField>Publicacion</DataField>
|
||||||
|
<rd:TypeName>System.String</rd:TypeName>
|
||||||
|
</Field>
|
||||||
|
<Field Name="Canilla">
|
||||||
|
<DataField>Canilla</DataField>
|
||||||
|
<rd:TypeName>System.String</rd:TypeName>
|
||||||
|
</Field>
|
||||||
|
<Field Name="TotalCantSalida">
|
||||||
|
<DataField>TotalCantSalida</DataField>
|
||||||
|
<rd:TypeName>System.Int32</rd:TypeName>
|
||||||
|
</Field>
|
||||||
|
<Field Name="TotalCantEntrada">
|
||||||
|
<DataField>TotalCantEntrada</DataField>
|
||||||
|
<rd:TypeName>System.Int32</rd:TypeName>
|
||||||
|
</Field>
|
||||||
|
<Field Name="TotalRendir">
|
||||||
|
<DataField>TotalRendir</DataField>
|
||||||
|
<rd:TypeName>System.Decimal</rd:TypeName>
|
||||||
|
</Field>
|
||||||
|
<Field Name="PrecioEjemplar">
|
||||||
|
<DataField>PrecioEjemplar</DataField>
|
||||||
|
<rd:TypeName>System.Decimal</rd:TypeName>
|
||||||
|
</Field>
|
||||||
|
</Fields>
|
||||||
|
<rd:DataSetInfo>
|
||||||
|
<rd:DataSetName>DSLiquidacionCanillas</rd:DataSetName>
|
||||||
|
<rd:SchemaPath>C:\Users\dmolinari\source\repos\Cobol-VBNet\Reportes\DSLiquidacionCanillas.xsd</rd:SchemaPath>
|
||||||
|
<rd:TableName>SP_DistCanillasLiquidacion</rd:TableName>
|
||||||
|
<rd:TableAdapterFillMethod>Fill</rd:TableAdapterFillMethod>
|
||||||
|
<rd:TableAdapterGetDataMethod>GetData</rd:TableAdapterGetDataMethod>
|
||||||
|
<rd:TableAdapterName>SP_DistCanillasLiquidacionTableAdapter</rd:TableAdapterName>
|
||||||
|
</rd:DataSetInfo>
|
||||||
|
</DataSet>
|
||||||
|
</DataSets>
|
||||||
|
<ReportSections>
|
||||||
|
<ReportSection>
|
||||||
|
<Body>
|
||||||
|
<ReportItems>
|
||||||
|
<Tablix Name="Tablix1">
|
||||||
|
<TablixBody>
|
||||||
|
<TablixColumns>
|
||||||
|
<TablixColumn>
|
||||||
|
<Width>3.53124cm</Width>
|
||||||
|
</TablixColumn>
|
||||||
|
<TablixColumn>
|
||||||
|
<Width>3.10854cm</Width>
|
||||||
|
</TablixColumn>
|
||||||
|
<TablixColumn>
|
||||||
|
<Width>3.55834cm</Width>
|
||||||
|
</TablixColumn>
|
||||||
|
</TablixColumns>
|
||||||
|
<TablixRows>
|
||||||
|
<TablixRow>
|
||||||
|
<Height>0.5cm</Height>
|
||||||
|
<TablixCells>
|
||||||
|
<TablixCell>
|
||||||
|
<CellContents>
|
||||||
|
<Textbox Name="Textbox38">
|
||||||
|
<CanGrow>true</CanGrow>
|
||||||
|
<KeepTogether>true</KeepTogether>
|
||||||
|
<Paragraphs>
|
||||||
|
<Paragraph>
|
||||||
|
<TextRuns>
|
||||||
|
<TextRun>
|
||||||
|
<Value>="Vendedor: " & First(Fields!Canilla.Value, "DSLiquidacionCanillas")</Value>
|
||||||
|
<Style>
|
||||||
|
<FontFamily>Roboto</FontFamily>
|
||||||
|
<FontSize>8pt</FontSize>
|
||||||
|
<FontWeight>Bold</FontWeight>
|
||||||
|
</Style>
|
||||||
|
</TextRun>
|
||||||
|
</TextRuns>
|
||||||
|
<Style />
|
||||||
|
</Paragraph>
|
||||||
|
</Paragraphs>
|
||||||
|
<rd:DefaultName>Textbox38</rd:DefaultName>
|
||||||
|
<Style>
|
||||||
|
<Border>
|
||||||
|
<Style>None</Style>
|
||||||
|
</Border>
|
||||||
|
<PaddingLeft>2pt</PaddingLeft>
|
||||||
|
<PaddingRight>2pt</PaddingRight>
|
||||||
|
<PaddingTop>2pt</PaddingTop>
|
||||||
|
<PaddingBottom>2pt</PaddingBottom>
|
||||||
|
</Style>
|
||||||
|
</Textbox>
|
||||||
|
<ColSpan>3</ColSpan>
|
||||||
|
</CellContents>
|
||||||
|
</TablixCell>
|
||||||
|
<TablixCell />
|
||||||
|
<TablixCell />
|
||||||
|
</TablixCells>
|
||||||
|
</TablixRow>
|
||||||
|
<TablixRow>
|
||||||
|
<Height>0.5cm</Height>
|
||||||
|
<TablixCells>
|
||||||
|
<TablixCell>
|
||||||
|
<CellContents>
|
||||||
|
<Textbox Name="Publicacion">
|
||||||
|
<CanGrow>true</CanGrow>
|
||||||
|
<KeepTogether>true</KeepTogether>
|
||||||
|
<Paragraphs>
|
||||||
|
<Paragraph>
|
||||||
|
<TextRuns>
|
||||||
|
<TextRun>
|
||||||
|
<Value>=Fields!Publicacion.Value</Value>
|
||||||
|
<Style>
|
||||||
|
<FontFamily>Roboto</FontFamily>
|
||||||
|
<FontSize>8pt</FontSize>
|
||||||
|
</Style>
|
||||||
|
</TextRun>
|
||||||
|
</TextRuns>
|
||||||
|
<Style />
|
||||||
|
</Paragraph>
|
||||||
|
</Paragraphs>
|
||||||
|
<rd:DefaultName>Publicacion</rd:DefaultName>
|
||||||
|
<Style>
|
||||||
|
<Border>
|
||||||
|
<Color>LightGrey</Color>
|
||||||
|
<Style>Solid</Style>
|
||||||
|
</Border>
|
||||||
|
<PaddingLeft>2pt</PaddingLeft>
|
||||||
|
<PaddingRight>2pt</PaddingRight>
|
||||||
|
<PaddingTop>2pt</PaddingTop>
|
||||||
|
<PaddingBottom>2pt</PaddingBottom>
|
||||||
|
</Style>
|
||||||
|
</Textbox>
|
||||||
|
</CellContents>
|
||||||
|
</TablixCell>
|
||||||
|
<TablixCell>
|
||||||
|
<CellContents>
|
||||||
|
<Textbox Name="Textbox13">
|
||||||
|
<CanGrow>true</CanGrow>
|
||||||
|
<KeepTogether>true</KeepTogether>
|
||||||
|
<Paragraphs>
|
||||||
|
<Paragraph>
|
||||||
|
<TextRuns>
|
||||||
|
<TextRun>
|
||||||
|
<Value>Retirados</Value>
|
||||||
|
<Style>
|
||||||
|
<FontFamily>Roboto</FontFamily>
|
||||||
|
<FontSize>8pt</FontSize>
|
||||||
|
</Style>
|
||||||
|
</TextRun>
|
||||||
|
</TextRuns>
|
||||||
|
<Style />
|
||||||
|
</Paragraph>
|
||||||
|
</Paragraphs>
|
||||||
|
<rd:DefaultName>Textbox13</rd:DefaultName>
|
||||||
|
<Style>
|
||||||
|
<Border>
|
||||||
|
<Color>LightGrey</Color>
|
||||||
|
<Style>Solid</Style>
|
||||||
|
</Border>
|
||||||
|
<PaddingLeft>2pt</PaddingLeft>
|
||||||
|
<PaddingRight>2pt</PaddingRight>
|
||||||
|
<PaddingTop>2pt</PaddingTop>
|
||||||
|
<PaddingBottom>2pt</PaddingBottom>
|
||||||
|
</Style>
|
||||||
|
</Textbox>
|
||||||
|
</CellContents>
|
||||||
|
</TablixCell>
|
||||||
|
<TablixCell>
|
||||||
|
<CellContents>
|
||||||
|
<Textbox Name="TotalCantSalida">
|
||||||
|
<CanGrow>true</CanGrow>
|
||||||
|
<KeepTogether>true</KeepTogether>
|
||||||
|
<Paragraphs>
|
||||||
|
<Paragraph>
|
||||||
|
<TextRuns>
|
||||||
|
<TextRun>
|
||||||
|
<Value>=Fields!TotalCantSalida.Value</Value>
|
||||||
|
<Style>
|
||||||
|
<FontFamily>Roboto</FontFamily>
|
||||||
|
<FontSize>8pt</FontSize>
|
||||||
|
</Style>
|
||||||
|
</TextRun>
|
||||||
|
</TextRuns>
|
||||||
|
<Style />
|
||||||
|
</Paragraph>
|
||||||
|
</Paragraphs>
|
||||||
|
<rd:DefaultName>TotalCantSalida</rd:DefaultName>
|
||||||
|
<Style>
|
||||||
|
<Border>
|
||||||
|
<Color>LightGrey</Color>
|
||||||
|
<Style>Solid</Style>
|
||||||
|
</Border>
|
||||||
|
<PaddingLeft>2pt</PaddingLeft>
|
||||||
|
<PaddingRight>2pt</PaddingRight>
|
||||||
|
<PaddingTop>2pt</PaddingTop>
|
||||||
|
<PaddingBottom>2pt</PaddingBottom>
|
||||||
|
</Style>
|
||||||
|
</Textbox>
|
||||||
|
</CellContents>
|
||||||
|
</TablixCell>
|
||||||
|
</TablixCells>
|
||||||
|
</TablixRow>
|
||||||
|
<TablixRow>
|
||||||
|
<Height>0.5cm</Height>
|
||||||
|
<TablixCells>
|
||||||
|
<TablixCell>
|
||||||
|
<CellContents>
|
||||||
|
<Textbox Name="Textbox7">
|
||||||
|
<CanGrow>true</CanGrow>
|
||||||
|
<KeepTogether>true</KeepTogether>
|
||||||
|
<Paragraphs>
|
||||||
|
<Paragraph>
|
||||||
|
<TextRuns>
|
||||||
|
<TextRun>
|
||||||
|
<Value />
|
||||||
|
<Style>
|
||||||
|
<FontSize>8pt</FontSize>
|
||||||
|
</Style>
|
||||||
|
</TextRun>
|
||||||
|
</TextRuns>
|
||||||
|
<Style />
|
||||||
|
</Paragraph>
|
||||||
|
</Paragraphs>
|
||||||
|
<rd:DefaultName>Textbox7</rd:DefaultName>
|
||||||
|
<Style>
|
||||||
|
<Border>
|
||||||
|
<Color>LightGrey</Color>
|
||||||
|
<Style>Solid</Style>
|
||||||
|
</Border>
|
||||||
|
<TopBorder>
|
||||||
|
<Style>None</Style>
|
||||||
|
</TopBorder>
|
||||||
|
<BottomBorder>
|
||||||
|
<Style>None</Style>
|
||||||
|
</BottomBorder>
|
||||||
|
<LeftBorder>
|
||||||
|
<Style>None</Style>
|
||||||
|
</LeftBorder>
|
||||||
|
<PaddingLeft>2pt</PaddingLeft>
|
||||||
|
<PaddingRight>2pt</PaddingRight>
|
||||||
|
<PaddingTop>2pt</PaddingTop>
|
||||||
|
<PaddingBottom>2pt</PaddingBottom>
|
||||||
|
</Style>
|
||||||
|
</Textbox>
|
||||||
|
</CellContents>
|
||||||
|
</TablixCell>
|
||||||
|
<TablixCell>
|
||||||
|
<CellContents>
|
||||||
|
<Textbox Name="Textbox14">
|
||||||
|
<CanGrow>true</CanGrow>
|
||||||
|
<KeepTogether>true</KeepTogether>
|
||||||
|
<Paragraphs>
|
||||||
|
<Paragraph>
|
||||||
|
<TextRuns>
|
||||||
|
<TextRun>
|
||||||
|
<Value>Devueltos</Value>
|
||||||
|
<Style>
|
||||||
|
<FontFamily>Roboto</FontFamily>
|
||||||
|
<FontSize>8pt</FontSize>
|
||||||
|
</Style>
|
||||||
|
</TextRun>
|
||||||
|
</TextRuns>
|
||||||
|
<Style />
|
||||||
|
</Paragraph>
|
||||||
|
</Paragraphs>
|
||||||
|
<rd:DefaultName>Textbox14</rd:DefaultName>
|
||||||
|
<Style>
|
||||||
|
<Border>
|
||||||
|
<Color>LightGrey</Color>
|
||||||
|
<Style>Solid</Style>
|
||||||
|
</Border>
|
||||||
|
<PaddingLeft>2pt</PaddingLeft>
|
||||||
|
<PaddingRight>2pt</PaddingRight>
|
||||||
|
<PaddingTop>2pt</PaddingTop>
|
||||||
|
<PaddingBottom>2pt</PaddingBottom>
|
||||||
|
</Style>
|
||||||
|
</Textbox>
|
||||||
|
</CellContents>
|
||||||
|
</TablixCell>
|
||||||
|
<TablixCell>
|
||||||
|
<CellContents>
|
||||||
|
<Textbox Name="Textbox12">
|
||||||
|
<CanGrow>true</CanGrow>
|
||||||
|
<KeepTogether>true</KeepTogether>
|
||||||
|
<Paragraphs>
|
||||||
|
<Paragraph>
|
||||||
|
<TextRuns>
|
||||||
|
<TextRun>
|
||||||
|
<Value>=Fields!TotalCantEntrada.Value</Value>
|
||||||
|
<Style>
|
||||||
|
<FontFamily>Roboto</FontFamily>
|
||||||
|
<FontSize>8pt</FontSize>
|
||||||
|
</Style>
|
||||||
|
</TextRun>
|
||||||
|
</TextRuns>
|
||||||
|
<Style />
|
||||||
|
</Paragraph>
|
||||||
|
</Paragraphs>
|
||||||
|
<rd:DefaultName>Textbox8</rd:DefaultName>
|
||||||
|
<Style>
|
||||||
|
<Border>
|
||||||
|
<Color>LightGrey</Color>
|
||||||
|
<Style>Solid</Style>
|
||||||
|
</Border>
|
||||||
|
<PaddingLeft>2pt</PaddingLeft>
|
||||||
|
<PaddingRight>2pt</PaddingRight>
|
||||||
|
<PaddingTop>2pt</PaddingTop>
|
||||||
|
<PaddingBottom>2pt</PaddingBottom>
|
||||||
|
</Style>
|
||||||
|
</Textbox>
|
||||||
|
</CellContents>
|
||||||
|
</TablixCell>
|
||||||
|
</TablixCells>
|
||||||
|
</TablixRow>
|
||||||
|
<TablixRow>
|
||||||
|
<Height>0.5cm</Height>
|
||||||
|
<TablixCells>
|
||||||
|
<TablixCell>
|
||||||
|
<CellContents>
|
||||||
|
<Textbox Name="Textbox27">
|
||||||
|
<CanGrow>true</CanGrow>
|
||||||
|
<KeepTogether>true</KeepTogether>
|
||||||
|
<Paragraphs>
|
||||||
|
<Paragraph>
|
||||||
|
<TextRuns>
|
||||||
|
<TextRun>
|
||||||
|
<Value />
|
||||||
|
<Style>
|
||||||
|
<FontSize>8pt</FontSize>
|
||||||
|
</Style>
|
||||||
|
</TextRun>
|
||||||
|
</TextRuns>
|
||||||
|
<Style />
|
||||||
|
</Paragraph>
|
||||||
|
</Paragraphs>
|
||||||
|
<rd:DefaultName>Textbox27</rd:DefaultName>
|
||||||
|
<Style>
|
||||||
|
<Border>
|
||||||
|
<Color>LightGrey</Color>
|
||||||
|
<Style>Solid</Style>
|
||||||
|
</Border>
|
||||||
|
<TopBorder>
|
||||||
|
<Style>None</Style>
|
||||||
|
</TopBorder>
|
||||||
|
<BottomBorder>
|
||||||
|
<Style>None</Style>
|
||||||
|
</BottomBorder>
|
||||||
|
<LeftBorder>
|
||||||
|
<Style>None</Style>
|
||||||
|
</LeftBorder>
|
||||||
|
<PaddingLeft>2pt</PaddingLeft>
|
||||||
|
<PaddingRight>2pt</PaddingRight>
|
||||||
|
<PaddingTop>2pt</PaddingTop>
|
||||||
|
<PaddingBottom>2pt</PaddingBottom>
|
||||||
|
</Style>
|
||||||
|
</Textbox>
|
||||||
|
</CellContents>
|
||||||
|
</TablixCell>
|
||||||
|
<TablixCell>
|
||||||
|
<CellContents>
|
||||||
|
<Textbox Name="Textbox28">
|
||||||
|
<CanGrow>true</CanGrow>
|
||||||
|
<KeepTogether>true</KeepTogether>
|
||||||
|
<Paragraphs>
|
||||||
|
<Paragraph>
|
||||||
|
<TextRuns>
|
||||||
|
<TextRun>
|
||||||
|
<Value>Vendidos</Value>
|
||||||
|
<Style>
|
||||||
|
<FontFamily>Roboto</FontFamily>
|
||||||
|
<FontSize>8pt</FontSize>
|
||||||
|
</Style>
|
||||||
|
</TextRun>
|
||||||
|
</TextRuns>
|
||||||
|
<Style />
|
||||||
|
</Paragraph>
|
||||||
|
</Paragraphs>
|
||||||
|
<rd:DefaultName>Textbox28</rd:DefaultName>
|
||||||
|
<Style>
|
||||||
|
<Border>
|
||||||
|
<Color>LightGrey</Color>
|
||||||
|
<Style>Solid</Style>
|
||||||
|
</Border>
|
||||||
|
<PaddingLeft>2pt</PaddingLeft>
|
||||||
|
<PaddingRight>2pt</PaddingRight>
|
||||||
|
<PaddingTop>2pt</PaddingTop>
|
||||||
|
<PaddingBottom>2pt</PaddingBottom>
|
||||||
|
</Style>
|
||||||
|
</Textbox>
|
||||||
|
</CellContents>
|
||||||
|
</TablixCell>
|
||||||
|
<TablixCell>
|
||||||
|
<CellContents>
|
||||||
|
<Textbox Name="Textbox29">
|
||||||
|
<CanGrow>true</CanGrow>
|
||||||
|
<KeepTogether>true</KeepTogether>
|
||||||
|
<Paragraphs>
|
||||||
|
<Paragraph>
|
||||||
|
<TextRuns>
|
||||||
|
<TextRun>
|
||||||
|
<Value>=Fields!TotalCantSalida.Value-Fields!TotalCantEntrada.Value</Value>
|
||||||
|
<Style>
|
||||||
|
<FontFamily>Roboto</FontFamily>
|
||||||
|
<FontSize>8pt</FontSize>
|
||||||
|
</Style>
|
||||||
|
</TextRun>
|
||||||
|
</TextRuns>
|
||||||
|
<Style />
|
||||||
|
</Paragraph>
|
||||||
|
</Paragraphs>
|
||||||
|
<rd:DefaultName>Textbox29</rd:DefaultName>
|
||||||
|
<Style>
|
||||||
|
<Border>
|
||||||
|
<Color>LightGrey</Color>
|
||||||
|
<Style>Solid</Style>
|
||||||
|
</Border>
|
||||||
|
<PaddingLeft>2pt</PaddingLeft>
|
||||||
|
<PaddingRight>2pt</PaddingRight>
|
||||||
|
<PaddingTop>2pt</PaddingTop>
|
||||||
|
<PaddingBottom>2pt</PaddingBottom>
|
||||||
|
</Style>
|
||||||
|
</Textbox>
|
||||||
|
</CellContents>
|
||||||
|
</TablixCell>
|
||||||
|
</TablixCells>
|
||||||
|
</TablixRow>
|
||||||
|
<TablixRow>
|
||||||
|
<Height>0.5cm</Height>
|
||||||
|
<TablixCells>
|
||||||
|
<TablixCell>
|
||||||
|
<CellContents>
|
||||||
|
<Textbox Name="Textbox33">
|
||||||
|
<CanGrow>true</CanGrow>
|
||||||
|
<KeepTogether>true</KeepTogether>
|
||||||
|
<Paragraphs>
|
||||||
|
<Paragraph>
|
||||||
|
<TextRuns>
|
||||||
|
<TextRun>
|
||||||
|
<Value />
|
||||||
|
<Style>
|
||||||
|
<FontSize>8pt</FontSize>
|
||||||
|
</Style>
|
||||||
|
</TextRun>
|
||||||
|
</TextRuns>
|
||||||
|
<Style />
|
||||||
|
</Paragraph>
|
||||||
|
</Paragraphs>
|
||||||
|
<rd:DefaultName>Textbox33</rd:DefaultName>
|
||||||
|
<Style>
|
||||||
|
<Border>
|
||||||
|
<Color>LightGrey</Color>
|
||||||
|
<Style>Solid</Style>
|
||||||
|
</Border>
|
||||||
|
<TopBorder>
|
||||||
|
<Style>None</Style>
|
||||||
|
</TopBorder>
|
||||||
|
<BottomBorder>
|
||||||
|
<Style>None</Style>
|
||||||
|
</BottomBorder>
|
||||||
|
<LeftBorder>
|
||||||
|
<Style>None</Style>
|
||||||
|
</LeftBorder>
|
||||||
|
<PaddingLeft>2pt</PaddingLeft>
|
||||||
|
<PaddingRight>2pt</PaddingRight>
|
||||||
|
<PaddingTop>2pt</PaddingTop>
|
||||||
|
<PaddingBottom>2pt</PaddingBottom>
|
||||||
|
</Style>
|
||||||
|
</Textbox>
|
||||||
|
</CellContents>
|
||||||
|
</TablixCell>
|
||||||
|
<TablixCell>
|
||||||
|
<CellContents>
|
||||||
|
<Textbox Name="Textbox30">
|
||||||
|
<CanGrow>true</CanGrow>
|
||||||
|
<KeepTogether>true</KeepTogether>
|
||||||
|
<Paragraphs>
|
||||||
|
<Paragraph>
|
||||||
|
<TextRuns>
|
||||||
|
<TextRun>
|
||||||
|
<Value>Precio Unitario</Value>
|
||||||
|
<Style>
|
||||||
|
<FontFamily>Roboto</FontFamily>
|
||||||
|
<FontSize>8pt</FontSize>
|
||||||
|
</Style>
|
||||||
|
</TextRun>
|
||||||
|
</TextRuns>
|
||||||
|
<Style />
|
||||||
|
</Paragraph>
|
||||||
|
</Paragraphs>
|
||||||
|
<rd:DefaultName>Textbox30</rd:DefaultName>
|
||||||
|
<Style>
|
||||||
|
<Border>
|
||||||
|
<Color>LightGrey</Color>
|
||||||
|
<Style>Solid</Style>
|
||||||
|
</Border>
|
||||||
|
<PaddingLeft>2pt</PaddingLeft>
|
||||||
|
<PaddingRight>2pt</PaddingRight>
|
||||||
|
<PaddingTop>2pt</PaddingTop>
|
||||||
|
<PaddingBottom>2pt</PaddingBottom>
|
||||||
|
</Style>
|
||||||
|
</Textbox>
|
||||||
|
</CellContents>
|
||||||
|
</TablixCell>
|
||||||
|
<TablixCell>
|
||||||
|
<CellContents>
|
||||||
|
<Textbox Name="Textbox32">
|
||||||
|
<CanGrow>true</CanGrow>
|
||||||
|
<KeepTogether>true</KeepTogether>
|
||||||
|
<Paragraphs>
|
||||||
|
<Paragraph>
|
||||||
|
<TextRuns>
|
||||||
|
<TextRun>
|
||||||
|
<Value>=Fields!PrecioEjemplar.Value</Value>
|
||||||
|
<Style>
|
||||||
|
<FontFamily>Roboto</FontFamily>
|
||||||
|
<FontSize>8pt</FontSize>
|
||||||
|
<Format>'$'#,0.00;'$'-#,0.00</Format>
|
||||||
|
</Style>
|
||||||
|
</TextRun>
|
||||||
|
</TextRuns>
|
||||||
|
<Style />
|
||||||
|
</Paragraph>
|
||||||
|
</Paragraphs>
|
||||||
|
<rd:DefaultName>Textbox32</rd:DefaultName>
|
||||||
|
<Style>
|
||||||
|
<Border>
|
||||||
|
<Color>LightGrey</Color>
|
||||||
|
<Style>Solid</Style>
|
||||||
|
</Border>
|
||||||
|
<PaddingLeft>2pt</PaddingLeft>
|
||||||
|
<PaddingRight>2pt</PaddingRight>
|
||||||
|
<PaddingTop>2pt</PaddingTop>
|
||||||
|
<PaddingBottom>2pt</PaddingBottom>
|
||||||
|
<rd:FormatSymbolCulture>es-AR</rd:FormatSymbolCulture>
|
||||||
|
</Style>
|
||||||
|
</Textbox>
|
||||||
|
</CellContents>
|
||||||
|
</TablixCell>
|
||||||
|
</TablixCells>
|
||||||
|
</TablixRow>
|
||||||
|
<TablixRow>
|
||||||
|
<Height>0.5cm</Height>
|
||||||
|
<TablixCells>
|
||||||
|
<TablixCell>
|
||||||
|
<CellContents>
|
||||||
|
<Textbox Name="Textbox35">
|
||||||
|
<CanGrow>true</CanGrow>
|
||||||
|
<KeepTogether>true</KeepTogether>
|
||||||
|
<Paragraphs>
|
||||||
|
<Paragraph>
|
||||||
|
<TextRuns>
|
||||||
|
<TextRun>
|
||||||
|
<Value />
|
||||||
|
<Style />
|
||||||
|
</TextRun>
|
||||||
|
</TextRuns>
|
||||||
|
<Style />
|
||||||
|
</Paragraph>
|
||||||
|
</Paragraphs>
|
||||||
|
<rd:DefaultName>Textbox35</rd:DefaultName>
|
||||||
|
<Style>
|
||||||
|
<Border>
|
||||||
|
<Color>LightGrey</Color>
|
||||||
|
<Style>None</Style>
|
||||||
|
</Border>
|
||||||
|
<TopBorder>
|
||||||
|
<Style>None</Style>
|
||||||
|
</TopBorder>
|
||||||
|
<BottomBorder>
|
||||||
|
<Style>None</Style>
|
||||||
|
</BottomBorder>
|
||||||
|
<LeftBorder>
|
||||||
|
<Style>None</Style>
|
||||||
|
</LeftBorder>
|
||||||
|
<PaddingLeft>2pt</PaddingLeft>
|
||||||
|
<PaddingRight>2pt</PaddingRight>
|
||||||
|
<PaddingTop>2pt</PaddingTop>
|
||||||
|
<PaddingBottom>2pt</PaddingBottom>
|
||||||
|
</Style>
|
||||||
|
</Textbox>
|
||||||
|
</CellContents>
|
||||||
|
</TablixCell>
|
||||||
|
<TablixCell>
|
||||||
|
<CellContents>
|
||||||
|
<Textbox Name="Textbox36">
|
||||||
|
<CanGrow>true</CanGrow>
|
||||||
|
<KeepTogether>true</KeepTogether>
|
||||||
|
<Paragraphs>
|
||||||
|
<Paragraph>
|
||||||
|
<TextRuns>
|
||||||
|
<TextRun>
|
||||||
|
<Value>Importe Vendido</Value>
|
||||||
|
<Style>
|
||||||
|
<FontFamily>Roboto</FontFamily>
|
||||||
|
<FontSize>8pt</FontSize>
|
||||||
|
<FontWeight>Bold</FontWeight>
|
||||||
|
</Style>
|
||||||
|
</TextRun>
|
||||||
|
</TextRuns>
|
||||||
|
<Style />
|
||||||
|
</Paragraph>
|
||||||
|
</Paragraphs>
|
||||||
|
<rd:DefaultName>Textbox36</rd:DefaultName>
|
||||||
|
<Style>
|
||||||
|
<Border>
|
||||||
|
<Color>LightGrey</Color>
|
||||||
|
<Style>Solid</Style>
|
||||||
|
</Border>
|
||||||
|
<TopBorder>
|
||||||
|
<Color>Black</Color>
|
||||||
|
<Width>2pt</Width>
|
||||||
|
</TopBorder>
|
||||||
|
<PaddingLeft>2pt</PaddingLeft>
|
||||||
|
<PaddingRight>2pt</PaddingRight>
|
||||||
|
<PaddingTop>2pt</PaddingTop>
|
||||||
|
<PaddingBottom>2pt</PaddingBottom>
|
||||||
|
</Style>
|
||||||
|
</Textbox>
|
||||||
|
</CellContents>
|
||||||
|
</TablixCell>
|
||||||
|
<TablixCell>
|
||||||
|
<CellContents>
|
||||||
|
<Textbox Name="Textbox37">
|
||||||
|
<CanGrow>true</CanGrow>
|
||||||
|
<KeepTogether>true</KeepTogether>
|
||||||
|
<Paragraphs>
|
||||||
|
<Paragraph>
|
||||||
|
<TextRuns>
|
||||||
|
<TextRun>
|
||||||
|
<Value>=Fields!TotalRendir.Value</Value>
|
||||||
|
<Style>
|
||||||
|
<FontFamily>Roboto</FontFamily>
|
||||||
|
<FontSize>8pt</FontSize>
|
||||||
|
<FontWeight>Bold</FontWeight>
|
||||||
|
<Format>'$'#,0.00;'$'-#,0.00</Format>
|
||||||
|
</Style>
|
||||||
|
</TextRun>
|
||||||
|
</TextRuns>
|
||||||
|
<Style />
|
||||||
|
</Paragraph>
|
||||||
|
</Paragraphs>
|
||||||
|
<rd:DefaultName>Textbox37</rd:DefaultName>
|
||||||
|
<Style>
|
||||||
|
<Border>
|
||||||
|
<Color>LightGrey</Color>
|
||||||
|
<Style>Solid</Style>
|
||||||
|
</Border>
|
||||||
|
<TopBorder>
|
||||||
|
<Color>Black</Color>
|
||||||
|
<Width>2pt</Width>
|
||||||
|
</TopBorder>
|
||||||
|
<PaddingLeft>2pt</PaddingLeft>
|
||||||
|
<PaddingRight>2pt</PaddingRight>
|
||||||
|
<PaddingTop>2pt</PaddingTop>
|
||||||
|
<PaddingBottom>2pt</PaddingBottom>
|
||||||
|
<rd:FormatSymbolCulture>es-AR</rd:FormatSymbolCulture>
|
||||||
|
</Style>
|
||||||
|
</Textbox>
|
||||||
|
</CellContents>
|
||||||
|
</TablixCell>
|
||||||
|
</TablixCells>
|
||||||
|
</TablixRow>
|
||||||
|
<TablixRow>
|
||||||
|
<Height>0.5cm</Height>
|
||||||
|
<TablixCells>
|
||||||
|
<TablixCell>
|
||||||
|
<CellContents>
|
||||||
|
<Textbox Name="Textbox1">
|
||||||
|
<CanGrow>true</CanGrow>
|
||||||
|
<KeepTogether>true</KeepTogether>
|
||||||
|
<Paragraphs>
|
||||||
|
<Paragraph>
|
||||||
|
<TextRuns>
|
||||||
|
<TextRun>
|
||||||
|
<Value />
|
||||||
|
<Style />
|
||||||
|
</TextRun>
|
||||||
|
</TextRuns>
|
||||||
|
<Style />
|
||||||
|
</Paragraph>
|
||||||
|
</Paragraphs>
|
||||||
|
<rd:DefaultName>Textbox1</rd:DefaultName>
|
||||||
|
<Style>
|
||||||
|
<Border>
|
||||||
|
<Color>LightGrey</Color>
|
||||||
|
<Style>None</Style>
|
||||||
|
</Border>
|
||||||
|
<TopBorder>
|
||||||
|
<Style>None</Style>
|
||||||
|
</TopBorder>
|
||||||
|
<BottomBorder>
|
||||||
|
<Style>None</Style>
|
||||||
|
</BottomBorder>
|
||||||
|
<LeftBorder>
|
||||||
|
<Style>None</Style>
|
||||||
|
</LeftBorder>
|
||||||
|
<PaddingLeft>2pt</PaddingLeft>
|
||||||
|
<PaddingRight>2pt</PaddingRight>
|
||||||
|
<PaddingTop>2pt</PaddingTop>
|
||||||
|
<PaddingBottom>2pt</PaddingBottom>
|
||||||
|
</Style>
|
||||||
|
</Textbox>
|
||||||
|
</CellContents>
|
||||||
|
</TablixCell>
|
||||||
|
<TablixCell>
|
||||||
|
<CellContents>
|
||||||
|
<Textbox Name="Textbox2">
|
||||||
|
<CanGrow>true</CanGrow>
|
||||||
|
<KeepTogether>true</KeepTogether>
|
||||||
|
<Paragraphs>
|
||||||
|
<Paragraph>
|
||||||
|
<TextRuns>
|
||||||
|
<TextRun>
|
||||||
|
<Value>Total A Rendir</Value>
|
||||||
|
<Style>
|
||||||
|
<FontFamily>Roboto</FontFamily>
|
||||||
|
<FontSize>8pt</FontSize>
|
||||||
|
<FontWeight>Bold</FontWeight>
|
||||||
|
</Style>
|
||||||
|
</TextRun>
|
||||||
|
</TextRuns>
|
||||||
|
<Style />
|
||||||
|
</Paragraph>
|
||||||
|
</Paragraphs>
|
||||||
|
<rd:DefaultName>Textbox2</rd:DefaultName>
|
||||||
|
<Style>
|
||||||
|
<Border>
|
||||||
|
<Color>LightGrey</Color>
|
||||||
|
<Style>Solid</Style>
|
||||||
|
</Border>
|
||||||
|
<TopBorder>
|
||||||
|
<Color>Black</Color>
|
||||||
|
<Width>2pt</Width>
|
||||||
|
</TopBorder>
|
||||||
|
<PaddingLeft>2pt</PaddingLeft>
|
||||||
|
<PaddingRight>2pt</PaddingRight>
|
||||||
|
<PaddingTop>2pt</PaddingTop>
|
||||||
|
<PaddingBottom>2pt</PaddingBottom>
|
||||||
|
</Style>
|
||||||
|
</Textbox>
|
||||||
|
</CellContents>
|
||||||
|
</TablixCell>
|
||||||
|
<TablixCell>
|
||||||
|
<CellContents>
|
||||||
|
<Textbox Name="Textbox4">
|
||||||
|
<CanGrow>true</CanGrow>
|
||||||
|
<KeepTogether>true</KeepTogether>
|
||||||
|
<Paragraphs>
|
||||||
|
<Paragraph>
|
||||||
|
<TextRuns>
|
||||||
|
<TextRun>
|
||||||
|
<Value>=Sum(Fields!TotalRendir.Value, "DSLiquidacionCanillas")</Value>
|
||||||
|
<Style>
|
||||||
|
<FontFamily>Roboto</FontFamily>
|
||||||
|
<FontSize>8pt</FontSize>
|
||||||
|
<FontWeight>Bold</FontWeight>
|
||||||
|
<Format>'$'#,0.00;'$'-#,0.00</Format>
|
||||||
|
</Style>
|
||||||
|
</TextRun>
|
||||||
|
</TextRuns>
|
||||||
|
<Style />
|
||||||
|
</Paragraph>
|
||||||
|
</Paragraphs>
|
||||||
|
<rd:DefaultName>Textbox4</rd:DefaultName>
|
||||||
|
<Style>
|
||||||
|
<Border>
|
||||||
|
<Color>LightGrey</Color>
|
||||||
|
<Style>Solid</Style>
|
||||||
|
</Border>
|
||||||
|
<TopBorder>
|
||||||
|
<Color>Black</Color>
|
||||||
|
<Width>2pt</Width>
|
||||||
|
</TopBorder>
|
||||||
|
<PaddingLeft>2pt</PaddingLeft>
|
||||||
|
<PaddingRight>2pt</PaddingRight>
|
||||||
|
<PaddingTop>2pt</PaddingTop>
|
||||||
|
<PaddingBottom>2pt</PaddingBottom>
|
||||||
|
<rd:FormatSymbolCulture>es-AR</rd:FormatSymbolCulture>
|
||||||
|
</Style>
|
||||||
|
</Textbox>
|
||||||
|
</CellContents>
|
||||||
|
</TablixCell>
|
||||||
|
</TablixCells>
|
||||||
|
</TablixRow>
|
||||||
|
</TablixRows>
|
||||||
|
</TablixBody>
|
||||||
|
<TablixColumnHierarchy>
|
||||||
|
<TablixMembers>
|
||||||
|
<TablixMember />
|
||||||
|
<TablixMember />
|
||||||
|
<TablixMember />
|
||||||
|
</TablixMembers>
|
||||||
|
</TablixColumnHierarchy>
|
||||||
|
<TablixRowHierarchy>
|
||||||
|
<TablixMembers>
|
||||||
|
<TablixMember>
|
||||||
|
<KeepWithGroup>After</KeepWithGroup>
|
||||||
|
</TablixMember>
|
||||||
|
<TablixMember>
|
||||||
|
<Group Name="Publicacion">
|
||||||
|
<GroupExpressions>
|
||||||
|
<GroupExpression>=Fields!Publicacion.Value</GroupExpression>
|
||||||
|
</GroupExpressions>
|
||||||
|
</Group>
|
||||||
|
<SortExpressions>
|
||||||
|
<SortExpression>
|
||||||
|
<Value>=Fields!Publicacion.Value</Value>
|
||||||
|
</SortExpression>
|
||||||
|
</SortExpressions>
|
||||||
|
<TablixMembers>
|
||||||
|
<TablixMember />
|
||||||
|
<TablixMember />
|
||||||
|
<TablixMember />
|
||||||
|
<TablixMember />
|
||||||
|
<TablixMember />
|
||||||
|
</TablixMembers>
|
||||||
|
</TablixMember>
|
||||||
|
<TablixMember>
|
||||||
|
<KeepWithGroup>Before</KeepWithGroup>
|
||||||
|
</TablixMember>
|
||||||
|
</TablixMembers>
|
||||||
|
</TablixRowHierarchy>
|
||||||
|
<DataSetName>DSLiquidacionCanillas</DataSetName>
|
||||||
|
<Top>1.07592cm</Top>
|
||||||
|
<Left>2.09225cm</Left>
|
||||||
|
<Height>3.5cm</Height>
|
||||||
|
<Width>10.19813cm</Width>
|
||||||
|
<Style>
|
||||||
|
<Border>
|
||||||
|
<Style>None</Style>
|
||||||
|
</Border>
|
||||||
|
<FontSize>8pt</FontSize>
|
||||||
|
</Style>
|
||||||
|
</Tablix>
|
||||||
|
<Textbox Name="Textbox39">
|
||||||
|
<CanGrow>true</CanGrow>
|
||||||
|
<KeepTogether>true</KeepTogether>
|
||||||
|
<Paragraphs>
|
||||||
|
<Paragraph>
|
||||||
|
<TextRuns>
|
||||||
|
<TextRun>
|
||||||
|
<Value>EL DIA S.A.I.C. y F.</Value>
|
||||||
|
<Style>
|
||||||
|
<FontFamily>Roboto</FontFamily>
|
||||||
|
<FontSize>8pt</FontSize>
|
||||||
|
</Style>
|
||||||
|
</TextRun>
|
||||||
|
</TextRuns>
|
||||||
|
<Style />
|
||||||
|
</Paragraph>
|
||||||
|
</Paragraphs>
|
||||||
|
<rd:DefaultName>Textbox39</rd:DefaultName>
|
||||||
|
<Top>0.01025cm</Top>
|
||||||
|
<Left>2.09225cm</Left>
|
||||||
|
<Height>0.5cm</Height>
|
||||||
|
<Width>10.19813cm</Width>
|
||||||
|
<ZIndex>1</ZIndex>
|
||||||
|
<Style>
|
||||||
|
<Border>
|
||||||
|
<Style>None</Style>
|
||||||
|
</Border>
|
||||||
|
<PaddingLeft>2pt</PaddingLeft>
|
||||||
|
<PaddingRight>2pt</PaddingRight>
|
||||||
|
<PaddingTop>2pt</PaddingTop>
|
||||||
|
<PaddingBottom>2pt</PaddingBottom>
|
||||||
|
</Style>
|
||||||
|
</Textbox>
|
||||||
|
<Textbox Name="Textbox40">
|
||||||
|
<CanGrow>true</CanGrow>
|
||||||
|
<KeepTogether>true</KeepTogether>
|
||||||
|
<Paragraphs>
|
||||||
|
<Paragraph>
|
||||||
|
<TextRuns>
|
||||||
|
<TextRun>
|
||||||
|
<Value>Liquidación venta de diarios del:</Value>
|
||||||
|
<Style>
|
||||||
|
<FontFamily>Roboto</FontFamily>
|
||||||
|
<FontSize>8pt</FontSize>
|
||||||
|
</Style>
|
||||||
|
</TextRun>
|
||||||
|
</TextRuns>
|
||||||
|
<Style>
|
||||||
|
<TextAlign>Left</TextAlign>
|
||||||
|
</Style>
|
||||||
|
</Paragraph>
|
||||||
|
</Paragraphs>
|
||||||
|
<rd:DefaultName>Textbox39</rd:DefaultName>
|
||||||
|
<Top>0.53236cm</Top>
|
||||||
|
<Left>2.09225cm</Left>
|
||||||
|
<Height>0.5cm</Height>
|
||||||
|
<Width>4.43198cm</Width>
|
||||||
|
<ZIndex>2</ZIndex>
|
||||||
|
<Style>
|
||||||
|
<Border>
|
||||||
|
<Style>None</Style>
|
||||||
|
</Border>
|
||||||
|
<PaddingLeft>2pt</PaddingLeft>
|
||||||
|
<PaddingRight>2pt</PaddingRight>
|
||||||
|
<PaddingTop>2pt</PaddingTop>
|
||||||
|
<PaddingBottom>2pt</PaddingBottom>
|
||||||
|
</Style>
|
||||||
|
</Textbox>
|
||||||
|
<Textbox Name="Textbox41">
|
||||||
|
<CanGrow>true</CanGrow>
|
||||||
|
<KeepTogether>true</KeepTogether>
|
||||||
|
<Paragraphs>
|
||||||
|
<Paragraph>
|
||||||
|
<TextRuns>
|
||||||
|
<TextRun>
|
||||||
|
<Value>=Parameters!FechaLiqui.Value</Value>
|
||||||
|
<Style>
|
||||||
|
<FontFamily>Roboto</FontFamily>
|
||||||
|
<FontSize>8pt</FontSize>
|
||||||
|
<Format>dd/MM/yyy</Format>
|
||||||
|
</Style>
|
||||||
|
</TextRun>
|
||||||
|
</TextRuns>
|
||||||
|
<Style>
|
||||||
|
<TextAlign>Left</TextAlign>
|
||||||
|
</Style>
|
||||||
|
</Paragraph>
|
||||||
|
</Paragraphs>
|
||||||
|
<rd:DefaultName>Textbox41</rd:DefaultName>
|
||||||
|
<Top>0.53236cm</Top>
|
||||||
|
<Left>6.44662cm</Left>
|
||||||
|
<Height>0.5cm</Height>
|
||||||
|
<Width>3.86292cm</Width>
|
||||||
|
<ZIndex>3</ZIndex>
|
||||||
|
<Style>
|
||||||
|
<Border>
|
||||||
|
<Style>None</Style>
|
||||||
|
</Border>
|
||||||
|
<PaddingLeft>2pt</PaddingLeft>
|
||||||
|
<PaddingRight>2pt</PaddingRight>
|
||||||
|
<PaddingTop>2pt</PaddingTop>
|
||||||
|
<PaddingBottom>2pt</PaddingBottom>
|
||||||
|
</Style>
|
||||||
|
</Textbox>
|
||||||
|
</ReportItems>
|
||||||
|
<Height>4.66667cm</Height>
|
||||||
|
<Style />
|
||||||
|
</Body>
|
||||||
|
<Width>14.8cm</Width>
|
||||||
|
<Page>
|
||||||
|
<PageHeight>21cm</PageHeight>
|
||||||
|
<PageWidth>14.8cm</PageWidth>
|
||||||
|
<LeftMargin>0cm</LeftMargin>
|
||||||
|
<RightMargin>0cm</RightMargin>
|
||||||
|
<TopMargin>0.5cm</TopMargin>
|
||||||
|
<BottomMargin>0.5cm</BottomMargin>
|
||||||
|
<ColumnSpacing>0.13cm</ColumnSpacing>
|
||||||
|
<Style />
|
||||||
|
</Page>
|
||||||
|
</ReportSection>
|
||||||
|
</ReportSections>
|
||||||
|
<ReportParameters>
|
||||||
|
<ReportParameter Name="FechaLiqui">
|
||||||
|
<DataType>DateTime</DataType>
|
||||||
|
<Prompt>ReportParameter1</Prompt>
|
||||||
|
</ReportParameter>
|
||||||
|
</ReportParameters>
|
||||||
|
<ReportParametersLayout>
|
||||||
|
<GridLayoutDefinition>
|
||||||
|
<NumberOfColumns>4</NumberOfColumns>
|
||||||
|
<NumberOfRows>2</NumberOfRows>
|
||||||
|
<CellDefinitions>
|
||||||
|
<CellDefinition>
|
||||||
|
<ColumnIndex>0</ColumnIndex>
|
||||||
|
<RowIndex>0</RowIndex>
|
||||||
|
<ParameterName>FechaLiqui</ParameterName>
|
||||||
|
</CellDefinition>
|
||||||
|
</CellDefinitions>
|
||||||
|
</GridLayoutDefinition>
|
||||||
|
</ReportParametersLayout>
|
||||||
|
<rd:ReportUnitType>Cm</rd:ReportUnitType>
|
||||||
|
<rd:ReportID>3bfdc2c9-c7dc-47b8-b2b1-3512fa773a78</rd:ReportID>
|
||||||
|
</Report>
|
||||||
@@ -1384,5 +1384,78 @@ namespace GestionIntegral.Api.Controllers
|
|||||||
return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al generar el PDF del reporte.");
|
return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al generar el PDF del reporte.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet("ticket-liquidacion-canilla/pdf")]
|
||||||
|
[ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> GetTicketLiquidacionCanillaPdf(
|
||||||
|
[FromQuery] DateTime fecha,
|
||||||
|
[FromQuery] int idCanilla,
|
||||||
|
[FromQuery] bool esAccionista = false) // Añadir esAccionista
|
||||||
|
{
|
||||||
|
// Usar PermisoVerComprobanteLiquidacionCanilla o uno específico si lo creas
|
||||||
|
if (!TienePermiso(PermisoVerComprobanteLiquidacionCanilla)) return Forbid();
|
||||||
|
|
||||||
|
var (detalles, ganancias, error) = await _reportesService.ObtenerDatosTicketLiquidacionAsync(fecha, idCanilla);
|
||||||
|
|
||||||
|
if (error != null) return BadRequest(new { message = error });
|
||||||
|
|
||||||
|
// El PDF podría funcionar incluso si solo uno de los datasets tiene datos,
|
||||||
|
// pero es bueno verificar si al menos hay detalles.
|
||||||
|
if (detalles == null || !detalles.Any())
|
||||||
|
{
|
||||||
|
return NotFound(new { message = "No hay detalles de liquidación para generar el PDF." });
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
LocalReport report = new LocalReport();
|
||||||
|
string rdlcPath = esAccionista ?
|
||||||
|
"Controllers/Reportes/RDLC/ReporteLiquidacionCanillasAcc.rdlc" :
|
||||||
|
"Controllers/Reportes/RDLC/ReporteLiquidacionCanillas.rdlc";
|
||||||
|
|
||||||
|
if (!System.IO.File.Exists(rdlcPath))
|
||||||
|
{
|
||||||
|
_logger.LogError("Archivo RDLC no encontrado: {Path}", rdlcPath);
|
||||||
|
return StatusCode(StatusCodes.Status500InternalServerError, $"Archivo de reporte no encontrado: {System.IO.Path.GetFileName(rdlcPath)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var fs = new FileStream(rdlcPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
||||||
|
{
|
||||||
|
report.LoadReportDefinition(fs);
|
||||||
|
}
|
||||||
|
|
||||||
|
report.DataSources.Add(new ReportDataSource("DSLiquidacionCanillas", detalles));
|
||||||
|
if (!esAccionista) // El reporte de accionistas podría no usar el dataset de ganancias, o usar uno diferente
|
||||||
|
{
|
||||||
|
report.DataSources.Add(new ReportDataSource("DSLiquidacionCanillasGanancias", ganancias ?? new List<LiquidacionCanillaGananciaDto>()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var parameters = new List<ReportParameter>
|
||||||
|
{
|
||||||
|
// El RDLC espera "FechaLiqui"
|
||||||
|
new ReportParameter("FechaLiqui", fecha.ToString("dd/MM/yyyy"))
|
||||||
|
};
|
||||||
|
// El nombre del canilla ya está en el DataSet "DSLiquidacionCanillas" (campo "Canilla")
|
||||||
|
// Si el RDLC lo espera como parámetro, lo añadiríamos aquí.
|
||||||
|
// var canilla = await _canillaRepository.GetByIdAsync(idCanilla);
|
||||||
|
// parameters.Add(new ReportParameter("NombreCanillaParam", canilla?.NomApe ?? "N/A"));
|
||||||
|
|
||||||
|
report.SetParameters(parameters);
|
||||||
|
|
||||||
|
byte[] pdfBytes = report.Render("PDF");
|
||||||
|
string tipo = esAccionista ? "Accionista" : "Canillita";
|
||||||
|
string fileName = $"TicketLiquidacion_{tipo}_{idCanilla}_{fecha:yyyyMMdd}.pdf";
|
||||||
|
return File(pdfBytes, "application/pdf", fileName);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error al generar PDF para Ticket Liquidación Canilla. Fecha: {Fecha}, Canilla: {IdCanilla}", fecha, idCanilla);
|
||||||
|
return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al generar el PDF del ticket.");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -263,10 +263,8 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
|
|
||||||
public async Task<bool> DeleteAsync(int idParte, int idUsuario, IDbTransaction transaction)
|
public async Task<bool> DeleteAsync(int idParte, int idUsuario, IDbTransaction transaction)
|
||||||
{
|
{
|
||||||
var actual = await GetByIdAsync(idParte); // No necesita TX, solo para el historial
|
var actual = await GetByIdAsync(idParte); // Sigue siendo útil para el historial
|
||||||
if (actual == null) throw new KeyNotFoundException("Registro E/S Canilla no encontrado para eliminar.");
|
if (actual == null) throw new KeyNotFoundException("Registro E/S Canilla no encontrado para eliminar.");
|
||||||
if (actual.Liquidado) throw new InvalidOperationException("No se puede eliminar un movimiento liquidado.");
|
|
||||||
|
|
||||||
|
|
||||||
const string sqlDelete = "DELETE FROM dbo.dist_EntradasSalidasCanillas WHERE Id_Parte = @IdParteParam";
|
const string sqlDelete = "DELETE FROM dbo.dist_EntradasSalidasCanillas WHERE Id_Parte = @IdParteParam";
|
||||||
const string sqlHistorico = @"
|
const string sqlHistorico = @"
|
||||||
|
|||||||
@@ -15,5 +15,8 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
Task<bool> DeleteAsync(int id, int idUsuario, IDbTransaction transaction); // Borrado físico con historial
|
Task<bool> DeleteAsync(int id, int idUsuario, IDbTransaction transaction); // Borrado físico con historial
|
||||||
Task<bool> ExistsByNameAndEmpresaAsync(string nombre, int idEmpresa, int? excludeIdPublicacion = null);
|
Task<bool> ExistsByNameAndEmpresaAsync(string nombre, int idEmpresa, int? excludeIdPublicacion = null);
|
||||||
Task<bool> IsInUseAsync(int id);
|
Task<bool> IsInUseAsync(int id);
|
||||||
|
Task<IEnumerable<PublicacionDiaSemana>> GetConfiguracionDiasAsync(int idPublicacion);
|
||||||
|
Task<IEnumerable<int>> GetPublicacionesIdsPorDiaSemanaAsync(byte diaSemana); // Devuelve solo IDs
|
||||||
|
Task UpdateConfiguracionDiasAsync(int idPublicacion, IEnumerable<byte> diasActivos, IDbTransaction transaction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -267,5 +267,65 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
var rowsAffected = await connection.ExecuteAsync(sqlDelete, new { IdParam = id }, transaction);
|
var rowsAffected = await connection.ExecuteAsync(sqlDelete, new { IdParam = id }, transaction);
|
||||||
return rowsAffected == 1;
|
return rowsAffected == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<PublicacionDiaSemana>> GetConfiguracionDiasAsync(int idPublicacion)
|
||||||
|
{
|
||||||
|
const string sql = @"
|
||||||
|
SELECT IdPublicacionDia, Id_Publicacion AS IdPublicacion, DiaSemana, Activo
|
||||||
|
FROM dbo.dist_PublicacionDiaSemana
|
||||||
|
WHERE Id_Publicacion = @IdPublicacion AND Activo = 1;"; // Solo los activos
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
|
return await connection.QueryAsync<PublicacionDiaSemana>(sql, new { IdPublicacion = idPublicacion });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error al obtener configuración de días para Publicacion ID: {IdPublicacion}", idPublicacion);
|
||||||
|
return Enumerable.Empty<PublicacionDiaSemana>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<int>> GetPublicacionesIdsPorDiaSemanaAsync(byte diaSemana)
|
||||||
|
{
|
||||||
|
const string sql = @"
|
||||||
|
SELECT pds.Id_Publicacion
|
||||||
|
FROM dbo.dist_PublicacionDiaSemana pds
|
||||||
|
INNER JOIN dbo.dist_dtPublicaciones p ON pds.Id_Publicacion = p.Id_Publicacion
|
||||||
|
WHERE pds.DiaSemana = @DiaSemana AND pds.Activo = 1 AND (p.Habilitada = 1 OR p.Habilitada IS NULL);"; // Solo publicaciones habilitadas
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
|
return await connection.QueryAsync<int>(sql, new { DiaSemana = diaSemana });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error al obtener IDs de publicaciones por día de semana: {DiaSemana}", diaSemana);
|
||||||
|
return Enumerable.Empty<int>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UpdateConfiguracionDiasAsync(int idPublicacion, IEnumerable<byte> diasActivos, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
var connection = transaction.Connection!;
|
||||||
|
|
||||||
|
// 1. Eliminar configuraciones existentes para esta publicación (más simple que hacer upserts complejos)
|
||||||
|
const string sqlDelete = "DELETE FROM dbo.dist_PublicacionDiaSemana WHERE Id_Publicacion = @IdPublicacion;";
|
||||||
|
await connection.ExecuteAsync(sqlDelete, new { IdPublicacion = idPublicacion }, transaction);
|
||||||
|
|
||||||
|
// 2. Insertar las nuevas configuraciones activas
|
||||||
|
if (diasActivos != null && diasActivos.Any())
|
||||||
|
{
|
||||||
|
const string sqlInsert = @"
|
||||||
|
INSERT INTO dbo.dist_PublicacionDiaSemana (Id_Publicacion, DiaSemana, Activo)
|
||||||
|
VALUES (@IdPublicacion, @DiaSemana, 1);"; // Siempre activo al insertar
|
||||||
|
|
||||||
|
var insertTasks = diasActivos.Select(dia =>
|
||||||
|
connection.ExecuteAsync(sqlInsert, new { IdPublicacion = idPublicacion, DiaSemana = dia }, transaction)
|
||||||
|
);
|
||||||
|
await Task.WhenAll(insertTasks);
|
||||||
|
}
|
||||||
|
// No se necesita historial para esta tabla de configuración por ahora.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -41,5 +41,7 @@ namespace GestionIntegral.Api.Data.Repositories.Reportes
|
|||||||
Task<IEnumerable<ListadoDistribucionDistSimpleDto>> GetListadoDistribucionDistSimpleAsync(int idDistribuidor, int idPublicacion, DateTime fechaDesde, DateTime fechaHasta);
|
Task<IEnumerable<ListadoDistribucionDistSimpleDto>> GetListadoDistribucionDistSimpleAsync(int idDistribuidor, int idPublicacion, DateTime fechaDesde, DateTime fechaHasta);
|
||||||
Task<IEnumerable<ListadoDistribucionDistPromedioDiaDto>> GetListadoDistribucionDistPromedioDiaAsync(int idDistribuidor, int idPublicacion, DateTime fechaDesde, DateTime fechaHasta);
|
Task<IEnumerable<ListadoDistribucionDistPromedioDiaDto>> GetListadoDistribucionDistPromedioDiaAsync(int idDistribuidor, int idPublicacion, DateTime fechaDesde, DateTime fechaHasta);
|
||||||
Task<(IEnumerable<ListadoDistribucionDistSimpleDto> Simple, IEnumerable<ListadoDistribucionDistPromedioDiaDto> Promedios, string? Error)> ObtenerListadoDistribucionDistribuidoresAsync(int idDistribuidor, int idPublicacion, DateTime fechaDesde, DateTime fechaHasta);
|
Task<(IEnumerable<ListadoDistribucionDistSimpleDto> Simple, IEnumerable<ListadoDistribucionDistPromedioDiaDto> Promedios, string? Error)> ObtenerListadoDistribucionDistribuidoresAsync(int idDistribuidor, int idPublicacion, DateTime fechaDesde, DateTime fechaHasta);
|
||||||
|
Task<IEnumerable<LiquidacionCanillaDetalleDto>> GetLiquidacionCanillaDetalleAsync(DateTime fecha, int idCanilla);
|
||||||
|
Task<IEnumerable<LiquidacionCanillaGananciaDto>> GetLiquidacionCanillaGananciasAsync(DateTime fecha, int idCanilla);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -479,5 +479,41 @@ namespace GestionIntegral.Api.Data.Repositories.Reportes
|
|||||||
return (Enumerable.Empty<ListadoDistribucionDistSimpleDto>(), Enumerable.Empty<ListadoDistribucionDistPromedioDiaDto>(), "Error interno al generar el reporte.");
|
return (Enumerable.Empty<ListadoDistribucionDistSimpleDto>(), Enumerable.Empty<ListadoDistribucionDistPromedioDiaDto>(), "Error interno al generar el reporte.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<LiquidacionCanillaDetalleDto>> GetLiquidacionCanillaDetalleAsync(DateTime fecha, int idCanilla)
|
||||||
|
{
|
||||||
|
const string spName = "dbo.SP_DistCanillasLiquidacion";
|
||||||
|
var parameters = new DynamicParameters();
|
||||||
|
parameters.Add("@fecha", fecha, DbType.DateTime);
|
||||||
|
parameters.Add("@idCanilla", idCanilla, DbType.Int32);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _dbConnectionFactory.CreateConnection();
|
||||||
|
return await connection.QueryAsync<LiquidacionCanillaDetalleDto>(spName, parameters, commandType: CommandType.StoredProcedure);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error SP {SPName} para Liquidacion Canilla Detalle. Fecha: {Fecha}, Canilla: {IdCanilla}", spName, fecha, idCanilla);
|
||||||
|
return Enumerable.Empty<LiquidacionCanillaDetalleDto>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<LiquidacionCanillaGananciaDto>> GetLiquidacionCanillaGananciasAsync(DateTime fecha, int idCanilla)
|
||||||
|
{
|
||||||
|
const string spName = "dbo.SP_DistCanillasLiquidacionGanancias";
|
||||||
|
var parameters = new DynamicParameters();
|
||||||
|
parameters.Add("@fecha", fecha, DbType.DateTime);
|
||||||
|
parameters.Add("@idCanilla", idCanilla, DbType.Int32);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _dbConnectionFactory.CreateConnection();
|
||||||
|
return await connection.QueryAsync<LiquidacionCanillaGananciaDto>(spName, parameters, commandType: CommandType.StoredProcedure);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error SP {SPName} para Liquidacion Canilla Ganancias. Fecha: {Fecha}, Canilla: {IdCanilla}", spName, fecha, idCanilla);
|
||||||
|
return Enumerable.Empty<LiquidacionCanillaGananciaDto>();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace GestionIntegral.Api.Models.Distribucion
|
||||||
|
{
|
||||||
|
public class PublicacionDiaSemana
|
||||||
|
{
|
||||||
|
public int IdPublicacionDia { get; set; }
|
||||||
|
public int IdPublicacion { get; set; } // FK a dist_dtPublicaciones
|
||||||
|
public byte DiaSemana { get; set; } // 0 (Domingo) a 6 (Sábado)
|
||||||
|
public bool Activo { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,38 +1,56 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace GestionIntegral.Api.Dtos.Distribucion
|
namespace GestionIntegral.Api.Dtos.Distribucion
|
||||||
{
|
{
|
||||||
|
[CustomValidation(typeof(CreateBulkEntradaSalidaCanillaDto), nameof(ValidateNoDuplicatePublicationsInItems))]
|
||||||
public class CreateBulkEntradaSalidaCanillaDto
|
public class CreateBulkEntradaSalidaCanillaDto
|
||||||
{
|
{
|
||||||
[Required(ErrorMessage = "El ID del canillita es obligatorio.")]
|
[Required(ErrorMessage = "El ID del canillita es obligatorio.")]
|
||||||
public int IdCanilla { get; set; }
|
public int IdCanilla { get; set; }
|
||||||
|
|
||||||
[Required(ErrorMessage = "La fecha del movimiento es obligatoria.")]
|
[Required(ErrorMessage = "La fecha del movimiento es obligatoria.")]
|
||||||
public DateTime Fecha { get; set; } // Fecha común para todos los ítems
|
public DateTime Fecha { get; set; }
|
||||||
|
|
||||||
[Required(ErrorMessage = "Debe haber al menos un ítem de movimiento.")]
|
[Required(ErrorMessage = "Debe haber al menos un ítem de movimiento.")]
|
||||||
[MinLength(1, ErrorMessage = "Debe agregar al menos una publicación.")]
|
[MinLength(1, ErrorMessage = "Debe agregar al menos una publicación.")]
|
||||||
|
// La validación de cada item se hará por los atributos en EntradaSalidaCanillaItemDto
|
||||||
public List<EntradaSalidaCanillaItemDto> Items { get; set; } = new List<EntradaSalidaCanillaItemDto>();
|
public List<EntradaSalidaCanillaItemDto> Items { get; set; } = new List<EntradaSalidaCanillaItemDto>();
|
||||||
|
|
||||||
// Validar que no haya publicaciones duplicadas en la lista de items
|
public static ValidationResult? ValidateNoDuplicatePublicationsInItems(CreateBulkEntradaSalidaCanillaDto instanceToValidate, ValidationContext context)
|
||||||
[CustomValidation(typeof(CreateBulkEntradaSalidaCanillaDto), nameof(ValidateNoDuplicatePublications))]
|
{
|
||||||
public string? DuplicateError { get; set; }
|
if (instanceToValidate == null)
|
||||||
|
{
|
||||||
|
return new ValidationResult("El objeto principal de la solicitud es nulo.");
|
||||||
|
}
|
||||||
|
|
||||||
public static ValidationResult? ValidateNoDuplicatePublications(CreateBulkEntradaSalidaCanillaDto dto, ValidationContext context)
|
if (instanceToValidate.Items == null)
|
||||||
{
|
{
|
||||||
if (dto.Items != null)
|
return ValidationResult.Success; // O error si una lista nula de items es inválida
|
||||||
|
}
|
||||||
|
|
||||||
|
if (instanceToValidate.Items.Any())
|
||||||
{
|
{
|
||||||
var duplicatePublications = dto.Items
|
if (instanceToValidate.Items.Any(item => item == null))
|
||||||
|
{
|
||||||
|
return new ValidationResult("La lista de ítems contiene entradas nulas.", new[] { nameof(Items) });
|
||||||
|
}
|
||||||
|
|
||||||
|
var duplicateGroups = instanceToValidate.Items
|
||||||
|
.Where(item => item.IdPublicacion != 0) // Considerar solo IDs válidos si 0 no lo es
|
||||||
.GroupBy(item => item.IdPublicacion)
|
.GroupBy(item => item.IdPublicacion)
|
||||||
.Where(group => group.Count() > 1)
|
.Where(group => group.Count() > 1)
|
||||||
.Select(group => group.Key)
|
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
if (duplicatePublications.Any())
|
if (duplicateGroups.Any())
|
||||||
{
|
{
|
||||||
return new ValidationResult($"No puede agregar la misma publicación varias veces. Publicaciones duplicadas: {string.Join(", ", duplicatePublications)}");
|
var duplicatedIds = string.Join(", ", duplicateGroups.Select(g => g.Key));
|
||||||
|
return new ValidationResult(
|
||||||
|
$"No puede agregar la misma publicación varias veces. Publicaciones duplicadas IDs: {duplicatedIds}",
|
||||||
|
new[] { nameof(Items) }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ValidationResult.Success;
|
return ValidationResult.Success;
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ using System.ComponentModel.DataAnnotations;
|
|||||||
|
|
||||||
namespace GestionIntegral.Api.Dtos.Distribucion
|
namespace GestionIntegral.Api.Dtos.Distribucion
|
||||||
{
|
{
|
||||||
|
[CustomValidation(typeof(EntradaSalidaCanillaItemDto), nameof(ValidateCantidadesItem))] // Aplicar a nivel de clase
|
||||||
public class EntradaSalidaCanillaItemDto
|
public class EntradaSalidaCanillaItemDto
|
||||||
{
|
{
|
||||||
[Required(ErrorMessage = "El ID de la publicación es obligatorio.")]
|
[Required(ErrorMessage = "El ID de la publicación es obligatorio.")]
|
||||||
|
[Range(1, int.MaxValue, ErrorMessage = "El ID de la publicación debe ser válido.")] // Asegurar que no sea 0
|
||||||
public int IdPublicacion { get; set; }
|
public int IdPublicacion { get; set; }
|
||||||
|
|
||||||
[Required(ErrorMessage = "La cantidad de salida es obligatoria.")]
|
[Required(ErrorMessage = "La cantidad de salida es obligatoria.")]
|
||||||
@@ -17,17 +19,26 @@ namespace GestionIntegral.Api.Dtos.Distribucion
|
|||||||
public int CantEntrada { get; set; }
|
public int CantEntrada { get; set; }
|
||||||
|
|
||||||
[StringLength(150)]
|
[StringLength(150)]
|
||||||
public string? Observacion { get; set; } // Observación por línea
|
public string? Observacion { get; set; }
|
||||||
|
|
||||||
// Validar que CantEntrada no sea mayor que CantSalida
|
// Ya no necesitamos la propiedad CantidadesError para esta validación
|
||||||
[CustomValidation(typeof(EntradaSalidaCanillaItemDto), nameof(ValidateCantidades))]
|
// public string? CantidadesError { get; set; }
|
||||||
public string? CantidadesError { get; set; }
|
|
||||||
|
|
||||||
public static ValidationResult? ValidateCantidades(EntradaSalidaCanillaItemDto item, ValidationContext context)
|
public static ValidationResult? ValidateCantidadesItem(EntradaSalidaCanillaItemDto instanceToValidate, ValidationContext context)
|
||||||
{
|
{
|
||||||
if (item.CantEntrada > item.CantSalida)
|
if (instanceToValidate == null)
|
||||||
{
|
{
|
||||||
return new ValidationResult("La cantidad de entrada (devolución) no puede ser mayor a la cantidad de salida (retiro) para esta publicación.", new[] { nameof(CantEntrada) });
|
// Aunque el model binder debería crear la instancia, verificamos
|
||||||
|
return ValidationResult.Success; // O un error si una instancia nula es inválida por sí misma
|
||||||
|
}
|
||||||
|
|
||||||
|
if (instanceToValidate.CantEntrada > instanceToValidate.CantSalida)
|
||||||
|
{
|
||||||
|
// Asociar el error a ambas propiedades podría ser útil en la UI
|
||||||
|
return new ValidationResult(
|
||||||
|
"La cantidad de entrada (devolución) no puede ser mayor a la cantidad de salida (retiro).",
|
||||||
|
new[] { nameof(CantEntrada), nameof(CantSalida) }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return ValidationResult.Success;
|
return ValidationResult.Success;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace GestionIntegral.Api.Dtos.Distribucion
|
||||||
|
{
|
||||||
|
public class PublicacionDiaSemanaDto
|
||||||
|
{
|
||||||
|
public int IdPublicacionDia { get; set; } // Útil si se editan individualmente
|
||||||
|
public int IdPublicacion { get; set; }
|
||||||
|
public byte DiaSemana { get; set; } // 0 (Domingo) a 6 (Sábado)
|
||||||
|
public bool Activo { get; set; } = true; // Por defecto activo al crear/mostrar
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,25 +1,36 @@
|
|||||||
// Similar a E/S Distribuidores, la edición es limitada para no afectar cálculos complejos ya hechos.
|
|
||||||
// Principalmente para corregir cantidades si aún no está liquidado.
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
namespace GestionIntegral.Api.Dtos.Distribucion
|
namespace GestionIntegral.Api.Dtos.Distribucion
|
||||||
{
|
{
|
||||||
|
// Aplicar el CustomValidation a nivel de clase
|
||||||
|
[CustomValidation(typeof(UpdateEntradaSalidaCanillaDto), nameof(ValidateCantidades))]
|
||||||
public class UpdateEntradaSalidaCanillaDto
|
public class UpdateEntradaSalidaCanillaDto
|
||||||
{
|
{
|
||||||
[Required, Range(0, int.MaxValue)]
|
[Required, Range(0, int.MaxValue)]
|
||||||
public int CantSalida { get; set; }
|
public int CantSalida { get; set; }
|
||||||
|
|
||||||
[Required, Range(0, int.MaxValue)]
|
[Required, Range(0, int.MaxValue)]
|
||||||
public int CantEntrada { get; set; }
|
public int CantEntrada { get; set; }
|
||||||
|
|
||||||
[StringLength(150)]
|
[StringLength(150)]
|
||||||
public string? Observacion { get; set; }
|
public string? Observacion { get; set; }
|
||||||
|
|
||||||
[CustomValidation(typeof(UpdateEntradaSalidaCanillaDto), nameof(ValidateCantidades))]
|
// El método de validación ahora recibe la instancia completa del DTO
|
||||||
public string? CantidadesError { get; set; } // Dummy para validación
|
public static ValidationResult? ValidateCantidades(UpdateEntradaSalidaCanillaDto instanceToValidate, ValidationContext context)
|
||||||
|
{
|
||||||
|
if (instanceToValidate == null)
|
||||||
|
{
|
||||||
|
// No debería ocurrir si el model binding funcionó, pero es una buena práctica.
|
||||||
|
return ValidationResult.Success;
|
||||||
|
}
|
||||||
|
|
||||||
public static ValidationResult? ValidateCantidades(UpdateEntradaSalidaCanillaDto dto, ValidationContext context)
|
if (instanceToValidate.CantEntrada > instanceToValidate.CantSalida)
|
||||||
{
|
{
|
||||||
if (dto.CantEntrada > dto.CantSalida)
|
// Asociar el error a las propiedades relevantes si es posible y útil
|
||||||
{
|
return new ValidationResult(
|
||||||
return new ValidationResult("La cantidad de entrada no puede ser mayor a la de salida.");
|
"La cantidad de entrada no puede ser mayor a la de salida.",
|
||||||
|
new[] { nameof(CantEntrada), nameof(CantSalida) } // Opcional: nombres de miembros
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return ValidationResult.Success;
|
return ValidationResult.Success;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
// Este DTO se usará para enviar la lista completa de días activos para una publicación.
|
||||||
|
namespace GestionIntegral.Api.Dtos.Distribucion
|
||||||
|
{
|
||||||
|
public class UpdatePublicacionDiasSemanaRequestDto
|
||||||
|
{
|
||||||
|
// Lista de los días de la semana (0-6) en los que la publicación estará activa.
|
||||||
|
// Si un día no está en esta lista, se considerará inactivo o se eliminará la configuración para ese día.
|
||||||
|
public List<byte> DiasActivos { get; set; } = new List<byte>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
public class LiquidacionCanillaDetalleDto
|
||||||
|
{
|
||||||
|
public string Publicacion { get; set; } = string.Empty;
|
||||||
|
public string Canilla { get; set; } = string.Empty; // Para el nombre del canilla en el reporte
|
||||||
|
public int TotalCantSalida { get; set; }
|
||||||
|
public int TotalCantEntrada { get; set; }
|
||||||
|
public decimal TotalRendir { get; set; }
|
||||||
|
public decimal PrecioEjemplar { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
public class LiquidacionCanillaGananciaDto
|
||||||
|
{
|
||||||
|
public string Publicacion { get; set; } = string.Empty;
|
||||||
|
public decimal TotalRendir { get; set; } // Asumo que este es el 'monto de comisión'
|
||||||
|
}
|
||||||
@@ -225,7 +225,7 @@ namespace GestionIntegral.Api.Services.Distribucion
|
|||||||
|
|
||||||
transaction = connection.BeginTransaction();
|
transaction = connection.BeginTransaction();
|
||||||
|
|
||||||
var esExistente = await _esCanillaRepository.GetByIdAsync(idParte);
|
var esExistente = await _esCanillaRepository.GetByIdAsync(idParte); // Obtener el estado actual
|
||||||
if (esExistente == null)
|
if (esExistente == null)
|
||||||
{
|
{
|
||||||
if (transaction?.Connection != null) transaction.Rollback();
|
if (transaction?.Connection != null) transaction.Rollback();
|
||||||
@@ -234,17 +234,16 @@ namespace GestionIntegral.Api.Services.Distribucion
|
|||||||
|
|
||||||
if (esExistente.Liquidado)
|
if (esExistente.Liquidado)
|
||||||
{
|
{
|
||||||
// Permiso MC006 es para "Eliminar Movimientos de Canillita Liquidados"
|
if (!TienePermisoEspecifico("MC006")) // <--- AQUÍ ESTÁ LA VERIFICACIÓN
|
||||||
if (!TienePermisoEspecifico("MC006"))
|
|
||||||
{
|
{
|
||||||
if (transaction?.Connection != null) transaction.Rollback();
|
if (transaction?.Connection != null) transaction.Rollback();
|
||||||
return (false, "No tiene permiso para eliminar movimientos ya liquidados. Se requiere permiso especial (MC006) o ser SuperAdmin.");
|
return (false, "No tiene permiso para eliminar movimientos ya liquidados. Se requiere permiso especial (MC006) o ser SuperAdmin.");
|
||||||
}
|
}
|
||||||
_logger.LogWarning("Usuario ID {IdUsuario} está eliminando un movimiento LIQUIDADO (IDParte: {IdParte}). Permiso MC006 verificado.", idUsuario, idParte);
|
_logger.LogWarning("Usuario ID {IdUsuario} está eliminando un movimiento LIQUIDADO (IDParte: {IdParte}). Permiso MC006 verificado.", idUsuario, idParte);
|
||||||
}
|
}
|
||||||
// Si no está liquidado, el permiso MC004 ya fue verificado en el controlador.
|
// Si no está liquidado, el permiso MC004 ya fue verificado en el controlador (o debería serlo).
|
||||||
|
|
||||||
var eliminado = await _esCanillaRepository.DeleteAsync(idParte, idUsuario, transaction);
|
var eliminado = await _esCanillaRepository.DeleteAsync(idParte, idUsuario, transaction); // Ahora esto no lanzará la excepción por liquidado
|
||||||
if (!eliminado)
|
if (!eliminado)
|
||||||
{
|
{
|
||||||
// No es necesario hacer rollback aquí si DeleteAsync lanza una excepción,
|
// No es necesario hacer rollback aquí si DeleteAsync lanza una excepción,
|
||||||
|
|||||||
@@ -11,5 +11,8 @@ namespace GestionIntegral.Api.Services.Distribucion
|
|||||||
Task<(PublicacionDto? Publicacion, string? Error)> CrearAsync(CreatePublicacionDto createDto, int idUsuario);
|
Task<(PublicacionDto? Publicacion, string? Error)> CrearAsync(CreatePublicacionDto createDto, int idUsuario);
|
||||||
Task<(bool Exito, string? Error)> ActualizarAsync(int id, UpdatePublicacionDto updateDto, int idUsuario);
|
Task<(bool Exito, string? Error)> ActualizarAsync(int id, UpdatePublicacionDto updateDto, int idUsuario);
|
||||||
Task<(bool Exito, string? Error)> EliminarAsync(int id, int idUsuario);
|
Task<(bool Exito, string? Error)> EliminarAsync(int id, int idUsuario);
|
||||||
|
Task<IEnumerable<PublicacionDiaSemanaDto>> ObtenerConfiguracionDiasAsync(int idPublicacion);
|
||||||
|
Task<IEnumerable<PublicacionDto>> ObtenerPublicacionesPorDiaSemanaAsync(byte diaSemana); // Devolvemos el DTO completo
|
||||||
|
Task<(bool Exito, string? Error)> ActualizarConfiguracionDiasAsync(int idPublicacion, UpdatePublicacionDiasSemanaRequestDto requestDto, int idUsuario);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -202,5 +202,71 @@ namespace GestionIntegral.Api.Services.Distribucion
|
|||||||
return (false, $"Error interno al eliminar la publicación y sus dependencias: {ex.Message}");
|
return (false, $"Error interno al eliminar la publicación y sus dependencias: {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<PublicacionDiaSemanaDto>> ObtenerConfiguracionDiasAsync(int idPublicacion)
|
||||||
|
{
|
||||||
|
var configs = await _publicacionRepository.GetConfiguracionDiasAsync(idPublicacion);
|
||||||
|
return configs.Select(c => new PublicacionDiaSemanaDto
|
||||||
|
{
|
||||||
|
IdPublicacionDia = c.IdPublicacionDia,
|
||||||
|
IdPublicacion = c.IdPublicacion,
|
||||||
|
DiaSemana = c.DiaSemana,
|
||||||
|
Activo = c.Activo
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<PublicacionDto>> ObtenerPublicacionesPorDiaSemanaAsync(byte diaSemana)
|
||||||
|
{
|
||||||
|
// Obtener IDs de las publicaciones configuradas para ese día
|
||||||
|
var idsPublicaciones = await _publicacionRepository.GetPublicacionesIdsPorDiaSemanaAsync(diaSemana);
|
||||||
|
|
||||||
|
if (!idsPublicaciones.Any())
|
||||||
|
{
|
||||||
|
return Enumerable.Empty<PublicacionDto>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtener los detalles completos de esas publicaciones
|
||||||
|
// Podríamos optimizar esto si GetByIdAsync es eficiente o crear un GetByIdsAsync
|
||||||
|
var publicacionesTasks = idsPublicaciones.Select(id => ObtenerPorIdAsync(id));
|
||||||
|
var publicacionesResult = await Task.WhenAll(publicacionesTasks);
|
||||||
|
|
||||||
|
return publicacionesResult.Where(p => p != null).Select(p => p!); // Filtrar nulos y asegurar no nulabilidad
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task<(bool Exito, string? Error)> ActualizarConfiguracionDiasAsync(int idPublicacion, UpdatePublicacionDiasSemanaRequestDto requestDto, int idUsuario)
|
||||||
|
{
|
||||||
|
var publicacionExistente = await _publicacionRepository.GetByIdSimpleAsync(idPublicacion);
|
||||||
|
if (publicacionExistente == null)
|
||||||
|
{
|
||||||
|
return (false, "Publicación no encontrada.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validar que los días de la semana estén en el rango correcto (0-6)
|
||||||
|
if (requestDto.DiasActivos.Any(d => d > 6)) // byte no puede ser < 0
|
||||||
|
{
|
||||||
|
return (false, "Día de la semana inválido. Debe estar entre 0 (Domingo) y 6 (Sábado).");
|
||||||
|
}
|
||||||
|
|
||||||
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
|
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
|
||||||
|
using var transaction = connection.BeginTransaction();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _publicacionRepository.UpdateConfiguracionDiasAsync(idPublicacion, requestDto.DiasActivos.Distinct(), transaction);
|
||||||
|
// No se está implementando historial para PublicacionDiaSemana por ahora.
|
||||||
|
// Si se necesitara, se añadiría aquí una llamada al repositorio para insertar en la tabla _H.
|
||||||
|
|
||||||
|
transaction.Commit();
|
||||||
|
_logger.LogInformation("Configuración de días actualizada para Publicación ID {IdPublicacion} por Usuario ID {UserId}.", idPublicacion, idUsuario);
|
||||||
|
return (true, null);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
try { transaction.Rollback(); } catch { }
|
||||||
|
_logger.LogError(ex, "Error al actualizar configuración de días para Publicacion ID: {IdPublicacion}", idPublicacion);
|
||||||
|
return (false, $"Error interno al actualizar la configuración de días: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -63,5 +63,11 @@ namespace GestionIntegral.Api.Services.Reportes
|
|||||||
)> ObtenerReporteCuentasDistribuidorAsync(int idDistribuidor, int idEmpresa, DateTime fechaDesde, DateTime fechaHasta);
|
)> ObtenerReporteCuentasDistribuidorAsync(int idDistribuidor, int idEmpresa, DateTime fechaDesde, DateTime fechaHasta);
|
||||||
|
|
||||||
Task<(IEnumerable<ListadoDistribucionDistSimpleDto> Simple, IEnumerable<ListadoDistribucionDistPromedioDiaDto> Promedios, string? Error)> ObtenerListadoDistribucionDistribuidoresAsync(int idDistribuidor, int idPublicacion, DateTime fechaDesde, DateTime fechaHasta);
|
Task<(IEnumerable<ListadoDistribucionDistSimpleDto> Simple, IEnumerable<ListadoDistribucionDistPromedioDiaDto> Promedios, string? Error)> ObtenerListadoDistribucionDistribuidoresAsync(int idDistribuidor, int idPublicacion, DateTime fechaDesde, DateTime fechaHasta);
|
||||||
|
|
||||||
|
Task<(
|
||||||
|
IEnumerable<LiquidacionCanillaDetalleDto> Detalles,
|
||||||
|
IEnumerable<LiquidacionCanillaGananciaDto> Ganancias,
|
||||||
|
string? Error
|
||||||
|
)> ObtenerDatosTicketLiquidacionAsync(DateTime fecha, int idCanilla);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -449,5 +449,46 @@ namespace GestionIntegral.Api.Services.Reportes
|
|||||||
return (Enumerable.Empty<ListadoDistribucionDistSimpleDto>(), Enumerable.Empty<ListadoDistribucionDistPromedioDiaDto>(), "Error interno al generar el reporte.");
|
return (Enumerable.Empty<ListadoDistribucionDistSimpleDto>(), Enumerable.Empty<ListadoDistribucionDistPromedioDiaDto>(), "Error interno al generar el reporte.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<(
|
||||||
|
IEnumerable<LiquidacionCanillaDetalleDto> Detalles,
|
||||||
|
IEnumerable<LiquidacionCanillaGananciaDto> Ganancias,
|
||||||
|
string? Error
|
||||||
|
)> ObtenerDatosTicketLiquidacionAsync(DateTime fecha, int idCanilla)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var detallesTask = _reportesRepository.GetLiquidacionCanillaDetalleAsync(fecha, idCanilla);
|
||||||
|
var gananciasTask = _reportesRepository.GetLiquidacionCanillaGananciasAsync(fecha, idCanilla);
|
||||||
|
|
||||||
|
await Task.WhenAll(detallesTask, gananciasTask);
|
||||||
|
|
||||||
|
var detalles = await detallesTask;
|
||||||
|
var ganancias = await gananciasTask;
|
||||||
|
|
||||||
|
if ((detalles == null || !detalles.Any()) && (ganancias == null || !ganancias.Any()))
|
||||||
|
{
|
||||||
|
// Podrías optar por no devolver error aquí si es válido que uno de los dos esté vacío
|
||||||
|
// y manejarlo en el controlador o el RDLC.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convertir fechas a UTC si es necesario para el RDLC (aunque estos DTOs no tienen fechas)
|
||||||
|
|
||||||
|
return (
|
||||||
|
detalles ?? Enumerable.Empty<LiquidacionCanillaDetalleDto>(),
|
||||||
|
ganancias ?? Enumerable.Empty<LiquidacionCanillaGananciaDto>(),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error en ReportesService al obtener datos para Ticket Liquidación Canilla. Fecha: {Fecha}, Canilla: {IdCanilla}", fecha, idCanilla);
|
||||||
|
return (
|
||||||
|
Enumerable.Empty<LiquidacionCanillaDetalleDto>(),
|
||||||
|
Enumerable.Empty<LiquidacionCanillaGananciaDto>(),
|
||||||
|
"Error interno al obtener los datos para el ticket de liquidación."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ConnectionStrings": {
|
"ConnectionStrings": {
|
||||||
"DefaultConnection": "Server=TECNICA3;Database=gestionvbnet;User ID=apigestion;Password=1351;Encrypt=False;TrustServerCertificate=True;"
|
"DefaultConnection": "Server=TECNICA3;Database=gestionvbnet;User ID=apigestion;Password=1351;Encrypt=False;MultipleActiveResultSets=True;TrustServerCertificate=True;"
|
||||||
},
|
},
|
||||||
"Jwt": {
|
"Jwt": {
|
||||||
"Key": "badb1a38d221c9e23bcf70958840ca7f5a5dc54f2047dadf7ce45b578b5bc3e2",
|
"Key": "badb1a38d221c9e23bcf70958840ca7f5a5dc54f2047dadf7ce45b578b5bc3e2",
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ConnectionStrings": {
|
"ConnectionStrings": {
|
||||||
"DefaultConnection": "Server=TECNICA3;Database=gestionvbnet;User ID=apigestion;Password=1351;Encrypt=False;TrustServerCertificate=True;"
|
"DefaultConnection": "Server=TECNICA3;Database=gestionvbnet;User ID=apigestion;Password=1351;Encrypt=False;MultipleActiveResultSets=True;TrustServerCertificate=True;"
|
||||||
},
|
},
|
||||||
"Jwt": {
|
"Jwt": {
|
||||||
"Key": "badb1a38d221c9e23bcf70958840ca7f5a5dc54f2047dadf7ce45b578b5bc3e2",
|
"Key": "badb1a38d221c9e23bcf70958840ca7f5a5dc54f2047dadf7ce45b578b5bc3e2",
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ using System.Reflection;
|
|||||||
[assembly: System.Reflection.AssemblyCompanyAttribute("GestionIntegral.Api")]
|
[assembly: System.Reflection.AssemblyCompanyAttribute("GestionIntegral.Api")]
|
||||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+1182a4cdee4fcdb55dc3f2dbfeeb2ec2187f2bea")]
|
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+99532b03f191d55f42e738a90b97f9f1e0dc1a9c")]
|
||||||
[assembly: System.Reflection.AssemblyProductAttribute("GestionIntegral.Api")]
|
[assembly: System.Reflection.AssemblyProductAttribute("GestionIntegral.Api")]
|
||||||
[assembly: System.Reflection.AssemblyTitleAttribute("GestionIntegral.Api")]
|
[assembly: System.Reflection.AssemblyTitleAttribute("GestionIntegral.Api")]
|
||||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{"GlobalPropertiesHash":"C9goqBDGh4B0L1HpPwpJHjfbRNoIuzqnU7zFMHk1LhM=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["lgiSIq1Xdt6PC6CpA82eiZlqBZS3M8jckHELlrL00LI=","bxlPVWHR7EivQofjz9PzA8dMpKpZqCfOZ\u002BHD\u002Bf1Ew9Y=","\u002BzMwu5DIAA49kPmSydn2WMzj\u002Bdcf0MC3YakKoR6HwYg=","FUb20tYUiusFv5/KhAPdh2OB4ArUWiGApXbQJdx8tX0=","pTWqrhLBwEeWg1GsRlTKzfOAnT1JEklZ8F1/EYlc1Nk=","Hu0oNH4YYNcbnR5Ts4qd5yzC5j5JbY2kEDXces8V1vs=","TKMARE0bLM2dm9NOqxxWztnuqao5IvCh24TEHCtht6I=","84UEEMEbmmNwHVXD5Iw3dtKHTZC0Zqbk3rIRO\u002BxOq4o=","qfTzsJ\u002B5ilLyrc6EhNm61KkSH37yRi85MtgW1\u002BUD2Vo=","4ayt/JAApEOfr0yjg9szkYMPzSs6x2k3QEwmrK5RZVY=","d0weYwKWe3mH5R2BURuNLkAyytO/viA6zivv9AcIBtQ=","Ssyx6SvSGgWMOzhc9pQpk6f6\u002BmVbKQNKeDJbvVA2tjs=","FSqDybxILZmKXw160ANhj76usnM83geRrbPvJxr89OA=","fdI2RZZ9M9QOVHCYU5cE\u002BgVVuT7ssRbMzdXvX8rHofc=","8ePFhqKT0OT9nEg3b5T7COC81U\u002BQBcf\u002BindBGyMy6z0=","/ghcduGmSd1I25YtYli\u002BqxF0xuscxc4cTDkbEC6XYVA=","/a3YEu0oBUeA5Qr2VMdppqLuz4CQPWJt2JfBl2dtUwA=","jEO/q4IO3UFTWxlyFwRr7kbGWcTIiS\u002BClxx3kahX/Fk=","4iYOCKYvhsROdGkA1hINVBejb6r8IkwFj9SNMKub3DM=","CeDswsZIn5a7t\u002BKeHJA222yhFvDVVEW1ky98Xxnxebc=","50j34YXOc950QSqaQBMtgezD3tV5mWWR9c5qZcYQoz4=","W/aX9jIKpjNEVoGrU6RXFOY8SDJVT6XB4Rg4QCaeQkQ=","16IbB\u002B3zYHZvsWbCQK6hBFmKJ6Z28SecBn2jm8R3w8I=","COJtHNQqycTJqXkFv2hhpLUT\u002B/AD4IWyQlmxkUVQPNk=","cp6a5bdvkLnUn3x47KQODzPycnx57RmWO\u002B9q8MuoGQo=","oKZRNhIQRaZrETEa3L6JiwIp0\u002BmjzJo193EWBoCuVUg=","sjwbCAEQX51sEWhYVGBihWUNBxniUKZALVJIGK\u002BYgsk=","A4m4kVcox60bvdkJ1CswoZADAT70WPcs4TAKdpMoUjM=","zSzyOuNcK0NQJLwK8Yg4sH4EflX7RPf65Fl2CZUWIGs=","OZUau2FUwouOUoP6Eot2qiZlqRHSBBkSPL6vHtWUfGI="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
{"GlobalPropertiesHash":"C9goqBDGh4B0L1HpPwpJHjfbRNoIuzqnU7zFMHk1LhM=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["V/5slELlkFDzZ8iiVKV8Jt0Ia8AL5AZxPCWo9apx5lQ=","bxlPVWHR7EivQofjz9PzA8dMpKpZqCfOZ\u002BHD\u002Bf1Ew9Y=","\u002BzMwu5DIAA49kPmSydn2WMzj\u002Bdcf0MC3YakKoR6HwYg=","FUb20tYUiusFv5/KhAPdh2OB4ArUWiGApXbQJdx8tX0=","pTWqrhLBwEeWg1GsRlTKzfOAnT1JEklZ8F1/EYlc1Nk=","Hu0oNH4YYNcbnR5Ts4qd5yzC5j5JbY2kEDXces8V1vs=","TKMARE0bLM2dm9NOqxxWztnuqao5IvCh24TEHCtht6I=","84UEEMEbmmNwHVXD5Iw3dtKHTZC0Zqbk3rIRO\u002BxOq4o=","qfTzsJ\u002B5ilLyrc6EhNm61KkSH37yRi85MtgW1\u002BUD2Vo=","4ayt/JAApEOfr0yjg9szkYMPzSs6x2k3QEwmrK5RZVY=","d0weYwKWe3mH5R2BURuNLkAyytO/viA6zivv9AcIBtQ=","Ssyx6SvSGgWMOzhc9pQpk6f6\u002BmVbKQNKeDJbvVA2tjs=","FSqDybxILZmKXw160ANhj76usnM83geRrbPvJxr89OA=","k3qzLxTWHeeJhAuWKMdta6j24bmJ9BMRMjuFEEVCRu0=","x/sHyso3gy4zVCu3ljpnTYCqu8IGZNRok1JoXiabIP8=","fdI2RZZ9M9QOVHCYU5cE\u002BgVVuT7ssRbMzdXvX8rHofc=","8ePFhqKT0OT9nEg3b5T7COC81U\u002BQBcf\u002BindBGyMy6z0=","/ghcduGmSd1I25YtYli\u002BqxF0xuscxc4cTDkbEC6XYVA=","/a3YEu0oBUeA5Qr2VMdppqLuz4CQPWJt2JfBl2dtUwA=","jEO/q4IO3UFTWxlyFwRr7kbGWcTIiS\u002BClxx3kahX/Fk=","4iYOCKYvhsROdGkA1hINVBejb6r8IkwFj9SNMKub3DM=","CeDswsZIn5a7t\u002BKeHJA222yhFvDVVEW1ky98Xxnxebc=","50j34YXOc950QSqaQBMtgezD3tV5mWWR9c5qZcYQoz4=","W/aX9jIKpjNEVoGrU6RXFOY8SDJVT6XB4Rg4QCaeQkQ=","16IbB\u002B3zYHZvsWbCQK6hBFmKJ6Z28SecBn2jm8R3w8I=","COJtHNQqycTJqXkFv2hhpLUT\u002B/AD4IWyQlmxkUVQPNk=","cp6a5bdvkLnUn3x47KQODzPycnx57RmWO\u002B9q8MuoGQo=","oKZRNhIQRaZrETEa3L6JiwIp0\u002BmjzJo193EWBoCuVUg=","sjwbCAEQX51sEWhYVGBihWUNBxniUKZALVJIGK\u002BYgsk=","A4m4kVcox60bvdkJ1CswoZADAT70WPcs4TAKdpMoUjM=","zSzyOuNcK0NQJLwK8Yg4sH4EflX7RPf65Fl2CZUWIGs=","hUWSz0FL2Jxxt3J4cPvysP9naCA2/Cxeo8sJx8s2hvs="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||||
@@ -1 +1 @@
|
|||||||
{"GlobalPropertiesHash":"w3MBbMV9Msh0YEq9AW/8s16bzXJ93T9lMVXKPm/r6es=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["lgiSIq1Xdt6PC6CpA82eiZlqBZS3M8jckHELlrL00LI=","bxlPVWHR7EivQofjz9PzA8dMpKpZqCfOZ\u002BHD\u002Bf1Ew9Y=","\u002BzMwu5DIAA49kPmSydn2WMzj\u002Bdcf0MC3YakKoR6HwYg=","FUb20tYUiusFv5/KhAPdh2OB4ArUWiGApXbQJdx8tX0=","pTWqrhLBwEeWg1GsRlTKzfOAnT1JEklZ8F1/EYlc1Nk=","Hu0oNH4YYNcbnR5Ts4qd5yzC5j5JbY2kEDXces8V1vs=","TKMARE0bLM2dm9NOqxxWztnuqao5IvCh24TEHCtht6I=","84UEEMEbmmNwHVXD5Iw3dtKHTZC0Zqbk3rIRO\u002BxOq4o=","qfTzsJ\u002B5ilLyrc6EhNm61KkSH37yRi85MtgW1\u002BUD2Vo=","4ayt/JAApEOfr0yjg9szkYMPzSs6x2k3QEwmrK5RZVY=","d0weYwKWe3mH5R2BURuNLkAyytO/viA6zivv9AcIBtQ=","Ssyx6SvSGgWMOzhc9pQpk6f6\u002BmVbKQNKeDJbvVA2tjs=","FSqDybxILZmKXw160ANhj76usnM83geRrbPvJxr89OA=","fdI2RZZ9M9QOVHCYU5cE\u002BgVVuT7ssRbMzdXvX8rHofc=","8ePFhqKT0OT9nEg3b5T7COC81U\u002BQBcf\u002BindBGyMy6z0=","/ghcduGmSd1I25YtYli\u002BqxF0xuscxc4cTDkbEC6XYVA=","/a3YEu0oBUeA5Qr2VMdppqLuz4CQPWJt2JfBl2dtUwA=","jEO/q4IO3UFTWxlyFwRr7kbGWcTIiS\u002BClxx3kahX/Fk=","4iYOCKYvhsROdGkA1hINVBejb6r8IkwFj9SNMKub3DM=","CeDswsZIn5a7t\u002BKeHJA222yhFvDVVEW1ky98Xxnxebc=","50j34YXOc950QSqaQBMtgezD3tV5mWWR9c5qZcYQoz4=","W/aX9jIKpjNEVoGrU6RXFOY8SDJVT6XB4Rg4QCaeQkQ=","16IbB\u002B3zYHZvsWbCQK6hBFmKJ6Z28SecBn2jm8R3w8I=","COJtHNQqycTJqXkFv2hhpLUT\u002B/AD4IWyQlmxkUVQPNk=","cp6a5bdvkLnUn3x47KQODzPycnx57RmWO\u002B9q8MuoGQo=","oKZRNhIQRaZrETEa3L6JiwIp0\u002BmjzJo193EWBoCuVUg=","sjwbCAEQX51sEWhYVGBihWUNBxniUKZALVJIGK\u002BYgsk=","A4m4kVcox60bvdkJ1CswoZADAT70WPcs4TAKdpMoUjM=","zSzyOuNcK0NQJLwK8Yg4sH4EflX7RPf65Fl2CZUWIGs=","OZUau2FUwouOUoP6Eot2qiZlqRHSBBkSPL6vHtWUfGI="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
{"GlobalPropertiesHash":"w3MBbMV9Msh0YEq9AW/8s16bzXJ93T9lMVXKPm/r6es=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["V/5slELlkFDzZ8iiVKV8Jt0Ia8AL5AZxPCWo9apx5lQ=","bxlPVWHR7EivQofjz9PzA8dMpKpZqCfOZ\u002BHD\u002Bf1Ew9Y=","\u002BzMwu5DIAA49kPmSydn2WMzj\u002Bdcf0MC3YakKoR6HwYg=","FUb20tYUiusFv5/KhAPdh2OB4ArUWiGApXbQJdx8tX0=","pTWqrhLBwEeWg1GsRlTKzfOAnT1JEklZ8F1/EYlc1Nk=","Hu0oNH4YYNcbnR5Ts4qd5yzC5j5JbY2kEDXces8V1vs=","TKMARE0bLM2dm9NOqxxWztnuqao5IvCh24TEHCtht6I=","84UEEMEbmmNwHVXD5Iw3dtKHTZC0Zqbk3rIRO\u002BxOq4o=","qfTzsJ\u002B5ilLyrc6EhNm61KkSH37yRi85MtgW1\u002BUD2Vo=","4ayt/JAApEOfr0yjg9szkYMPzSs6x2k3QEwmrK5RZVY=","d0weYwKWe3mH5R2BURuNLkAyytO/viA6zivv9AcIBtQ=","Ssyx6SvSGgWMOzhc9pQpk6f6\u002BmVbKQNKeDJbvVA2tjs=","FSqDybxILZmKXw160ANhj76usnM83geRrbPvJxr89OA=","k3qzLxTWHeeJhAuWKMdta6j24bmJ9BMRMjuFEEVCRu0=","x/sHyso3gy4zVCu3ljpnTYCqu8IGZNRok1JoXiabIP8=","fdI2RZZ9M9QOVHCYU5cE\u002BgVVuT7ssRbMzdXvX8rHofc=","8ePFhqKT0OT9nEg3b5T7COC81U\u002BQBcf\u002BindBGyMy6z0=","/ghcduGmSd1I25YtYli\u002BqxF0xuscxc4cTDkbEC6XYVA=","/a3YEu0oBUeA5Qr2VMdppqLuz4CQPWJt2JfBl2dtUwA=","jEO/q4IO3UFTWxlyFwRr7kbGWcTIiS\u002BClxx3kahX/Fk=","4iYOCKYvhsROdGkA1hINVBejb6r8IkwFj9SNMKub3DM=","CeDswsZIn5a7t\u002BKeHJA222yhFvDVVEW1ky98Xxnxebc=","50j34YXOc950QSqaQBMtgezD3tV5mWWR9c5qZcYQoz4=","W/aX9jIKpjNEVoGrU6RXFOY8SDJVT6XB4Rg4QCaeQkQ=","16IbB\u002B3zYHZvsWbCQK6hBFmKJ6Z28SecBn2jm8R3w8I=","COJtHNQqycTJqXkFv2hhpLUT\u002B/AD4IWyQlmxkUVQPNk=","cp6a5bdvkLnUn3x47KQODzPycnx57RmWO\u002B9q8MuoGQo=","oKZRNhIQRaZrETEa3L6JiwIp0\u002BmjzJo193EWBoCuVUg=","sjwbCAEQX51sEWhYVGBihWUNBxniUKZALVJIGK\u002BYgsk=","A4m4kVcox60bvdkJ1CswoZADAT70WPcs4TAKdpMoUjM=","zSzyOuNcK0NQJLwK8Yg4sH4EflX7RPf65Fl2CZUWIGs=","hUWSz0FL2Jxxt3J4cPvysP9naCA2/Cxeo8sJx8s2hvs="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||||
@@ -1 +1 @@
|
|||||||
{"GlobalPropertiesHash":"nueagD6vos1qa5Z6EdwL+uix/UGN3umfwM2JskZDeIQ=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["lgiSIq1Xdt6PC6CpA82eiZlqBZS3M8jckHELlrL00LI=","bxlPVWHR7EivQofjz9PzA8dMpKpZqCfOZ\u002BHD\u002Bf1Ew9Y="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
{"GlobalPropertiesHash":"nueagD6vos1qa5Z6EdwL+uix/UGN3umfwM2JskZDeIQ=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["V/5slELlkFDzZ8iiVKV8Jt0Ia8AL5AZxPCWo9apx5lQ=","bxlPVWHR7EivQofjz9PzA8dMpKpZqCfOZ\u002BHD\u002Bf1Ew9Y="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||||
@@ -162,7 +162,7 @@ const ControlDevolucionesFormModal: React.FC<ControlDevolucionesFormModalProps>
|
|||||||
margin="dense" fullWidth error={!!localErrors.fecha} helperText={localErrors.fecha || ''}
|
margin="dense" fullWidth error={!!localErrors.fecha} helperText={localErrors.fecha || ''}
|
||||||
disabled={loading || isEditing} InputLabelProps={{ shrink: true }} autoFocus={!isEditing}
|
disabled={loading || isEditing} InputLabelProps={{ shrink: true }} autoFocus={!isEditing}
|
||||||
/>
|
/>
|
||||||
<TextField label="Entrada (Devolución Total)" type="number" value={entrada} required
|
<TextField label="Entrada (Por Remito)" type="number" value={entrada} required
|
||||||
onChange={(e) => {setEntrada(e.target.value); handleInputChange('entrada');}}
|
onChange={(e) => {setEntrada(e.target.value); handleInputChange('entrada');}}
|
||||||
margin="dense" fullWidth error={!!localErrors.entrada} helperText={localErrors.entrada || ''}
|
margin="dense" fullWidth error={!!localErrors.entrada} helperText={localErrors.entrada || ''}
|
||||||
disabled={loading} inputProps={{min:0}}
|
disabled={loading} inputProps={{min:0}}
|
||||||
@@ -172,7 +172,7 @@ const ControlDevolucionesFormModal: React.FC<ControlDevolucionesFormModalProps>
|
|||||||
margin="dense" fullWidth error={!!localErrors.sobrantes} helperText={localErrors.sobrantes || ''}
|
margin="dense" fullWidth error={!!localErrors.sobrantes} helperText={localErrors.sobrantes || ''}
|
||||||
disabled={loading} inputProps={{min:0}}
|
disabled={loading} inputProps={{min:0}}
|
||||||
/>
|
/>
|
||||||
<TextField label="Sin Cargo" type="number" value={sinCargo} required
|
<TextField label="Ejemplares Sin Cargo" type="number" value={sinCargo} required
|
||||||
onChange={(e) => {setSinCargo(e.target.value); handleInputChange('sinCargo');}}
|
onChange={(e) => {setSinCargo(e.target.value); handleInputChange('sinCargo');}}
|
||||||
margin="dense" fullWidth error={!!localErrors.sinCargo} helperText={localErrors.sinCargo || ''}
|
margin="dense" fullWidth error={!!localErrors.sinCargo} helperText={localErrors.sinCargo || ''}
|
||||||
disabled={loading} inputProps={{min:0}}
|
disabled={loading} inputProps={{min:0}}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
import DeleteIcon from '@mui/icons-material/Delete';
|
import DeleteIcon from '@mui/icons-material/Delete';
|
||||||
|
|
||||||
import type { EntradaSalidaCanillaDto } from '../../../models/dtos/Distribucion/EntradaSalidaCanillaDto';
|
import type { EntradaSalidaCanillaDto } from '../../../models/dtos/Distribucion/EntradaSalidaCanillaDto';
|
||||||
import type { UpdateEntradaSalidaCanillaDto } from '../../../models/dtos/Distribucion/UpdateEntradaSalidaCanillaDto';
|
import type { UpdateEntradaSalidaCanillaDto } from '../../../models/dtos/Distribucion/UpdateEntradaSalidaCanillaDto';
|
||||||
import type { PublicacionDto } from '../../../models/dtos/Distribucion/PublicacionDto';
|
import type { PublicacionDto } from '../../../models/dtos/Distribucion/PublicacionDto';
|
||||||
@@ -51,8 +50,8 @@ interface FormRowItem {
|
|||||||
|
|
||||||
const EntradaSalidaCanillaFormModal: React.FC<EntradaSalidaCanillaFormModalProps> = ({
|
const EntradaSalidaCanillaFormModal: React.FC<EntradaSalidaCanillaFormModalProps> = ({
|
||||||
open,
|
open,
|
||||||
onClose,
|
onClose, // Este onClose es el que se pasa desde GestionarEntradasSalidasCanillaPage
|
||||||
onSubmit,
|
onSubmit, // Este onSubmit es el que se pasa para la lógica de EDICIÓN
|
||||||
initialData,
|
initialData,
|
||||||
errorMessage: parentErrorMessage,
|
errorMessage: parentErrorMessage,
|
||||||
clearErrorMessage
|
clearErrorMessage
|
||||||
@@ -63,16 +62,18 @@ const EntradaSalidaCanillaFormModal: React.FC<EntradaSalidaCanillaFormModalProps
|
|||||||
const [editCantSalida, setEditCantSalida] = useState<string>('0');
|
const [editCantSalida, setEditCantSalida] = useState<string>('0');
|
||||||
const [editCantEntrada, setEditCantEntrada] = useState<string>('0');
|
const [editCantEntrada, setEditCantEntrada] = useState<string>('0');
|
||||||
const [editObservacion, setEditObservacion] = useState('');
|
const [editObservacion, setEditObservacion] = useState('');
|
||||||
const [items, setItems] = useState<FormRowItem[]>([]);
|
const [items, setItems] = useState<FormRowItem[]>([{ id: Date.now().toString(), idPublicacion: '', cantSalida: '0', cantEntrada: '0', observacion: '' }]); // Iniciar con una fila
|
||||||
const [publicaciones, setPublicaciones] = useState<PublicacionDto[]>([]);
|
const [publicaciones, setPublicaciones] = useState<PublicacionDto[]>([]);
|
||||||
const [canillitas, setCanillitas] = useState<CanillaDto[]>([]);
|
const [canillitas, setCanillitas] = useState<CanillaDto[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false); // Loading para submit
|
||||||
const [loadingDropdowns, setLoadingDropdowns] = useState(false);
|
const [loadingDropdowns, setLoadingDropdowns] = useState(false); // Loading para canillas/pubs
|
||||||
|
const [loadingItems, setLoadingItems] = useState(false); // Loading para pre-carga de items
|
||||||
const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({});
|
const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({});
|
||||||
const [modalSpecificApiError, setModalSpecificApiError] = useState<string | null>(null);
|
const [modalSpecificApiError, setModalSpecificApiError] = useState<string | null>(null);
|
||||||
|
|
||||||
const isEditing = Boolean(initialData);
|
const isEditing = Boolean(initialData);
|
||||||
|
|
||||||
|
// Efecto para cargar datos de dropdowns (Publicaciones, Canillitas) SOLO UNA VEZ o cuando open cambia a true
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchDropdownData = async () => {
|
const fetchDropdownData = async () => {
|
||||||
setLoadingDropdowns(true);
|
setLoadingDropdowns(true);
|
||||||
@@ -94,6 +95,13 @@ const EntradaSalidaCanillaFormModal: React.FC<EntradaSalidaCanillaFormModalProps
|
|||||||
|
|
||||||
if (open) {
|
if (open) {
|
||||||
fetchDropdownData();
|
fetchDropdownData();
|
||||||
|
}
|
||||||
|
}, [open]);
|
||||||
|
|
||||||
|
|
||||||
|
// Efecto para inicializar el formulario cuando se abre o cambia initialData
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) {
|
||||||
clearErrorMessage();
|
clearErrorMessage();
|
||||||
setModalSpecificApiError(null);
|
setModalSpecificApiError(null);
|
||||||
setLocalErrors({});
|
setLocalErrors({});
|
||||||
@@ -105,19 +113,53 @@ const EntradaSalidaCanillaFormModal: React.FC<EntradaSalidaCanillaFormModalProps
|
|||||||
setEditCantSalida(initialData.cantSalida?.toString() || '0');
|
setEditCantSalida(initialData.cantSalida?.toString() || '0');
|
||||||
setEditCantEntrada(initialData.cantEntrada?.toString() || '0');
|
setEditCantEntrada(initialData.cantEntrada?.toString() || '0');
|
||||||
setEditObservacion(initialData.observacion || '');
|
setEditObservacion(initialData.observacion || '');
|
||||||
setItems([]);
|
setItems([]); // En modo edición, no pre-cargamos items de la lista
|
||||||
} else {
|
} else {
|
||||||
setItems([{ id: Date.now().toString(), idPublicacion: '', cantSalida: '0', cantEntrada: '0', observacion: '' }]);
|
// Modo NUEVO: resetear campos principales y dejar que el efecto de 'fecha' cargue los items
|
||||||
setIdCanilla('');
|
setIdCanilla('');
|
||||||
setFecha(new Date().toISOString().split('T')[0]);
|
setFecha(new Date().toISOString().split('T')[0]); // Fecha actual por defecto
|
||||||
setEditCantSalida('0');
|
// Los items se cargarán por el siguiente useEffect basado en la fecha
|
||||||
setEditCantEntrada('0');
|
|
||||||
setEditObservacion('');
|
|
||||||
setEditIdPublicacion('');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [open, initialData, isEditing, clearErrorMessage]);
|
}, [open, initialData, isEditing, clearErrorMessage]);
|
||||||
|
|
||||||
|
|
||||||
|
// Efecto para pre-cargar/re-cargar items cuando cambia la FECHA (en modo NUEVO)
|
||||||
|
// y cuando las publicaciones están disponibles.
|
||||||
|
useEffect(() => {
|
||||||
|
if (open && !isEditing && publicaciones.length > 0 && fecha) { // Asegurarse que 'fecha' tiene un valor
|
||||||
|
const diaSemana = new Date(fecha + 'T00:00:00Z').getUTCDay(); // Usar UTC para getDay consistente
|
||||||
|
setLoadingItems(true); // Indicador de carga para los items
|
||||||
|
setLocalErrors(prev => ({ ...prev, general: null }));
|
||||||
|
|
||||||
|
publicacionService.getPublicacionesPorDiaSemana(diaSemana)
|
||||||
|
.then(pubsPorDefecto => {
|
||||||
|
if (pubsPorDefecto.length > 0) {
|
||||||
|
const itemsPorDefecto = pubsPorDefecto.map(pub => ({
|
||||||
|
id: `${Date.now().toString()}-${pub.idPublicacion}`,
|
||||||
|
idPublicacion: pub.idPublicacion,
|
||||||
|
cantSalida: '0',
|
||||||
|
cantEntrada: '0',
|
||||||
|
observacion: ''
|
||||||
|
}));
|
||||||
|
setItems(itemsPorDefecto);
|
||||||
|
} else {
|
||||||
|
// Si no hay configuraciones para el día, iniciar con una fila vacía
|
||||||
|
setItems([{ id: Date.now().toString(), idPublicacion: '', cantSalida: '0', cantEntrada: '0', observacion: '' }]);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error("Error al cargar/recargar publicaciones por defecto para el día:", err);
|
||||||
|
setLocalErrors(prev => ({ ...prev, general: 'Error al pre-cargar publicaciones del día.' }));
|
||||||
|
setItems([{ id: Date.now().toString(), idPublicacion: '', cantSalida: '0', cantEntrada: '0', observacion: '' }]);
|
||||||
|
})
|
||||||
|
.finally(() => setLoadingItems(false));
|
||||||
|
} else if (open && !isEditing && publicaciones.length === 0 && !loadingDropdowns) {
|
||||||
|
// Si las publicaciones aún no se cargaron pero los dropdowns terminaron de cargar, iniciar con 1 item vacío.
|
||||||
|
setItems([{ id: Date.now().toString(), idPublicacion: '', cantSalida: '0', cantEntrada: '0', observacion: '' }]);
|
||||||
|
}
|
||||||
|
}, [open, isEditing, fecha, publicaciones, loadingDropdowns]); // Dependencias clave
|
||||||
|
|
||||||
const validate = (): boolean => {
|
const validate = (): boolean => {
|
||||||
const currentErrors: { [key: string]: string | null } = {};
|
const currentErrors: { [key: string]: string | null } = {};
|
||||||
if (!idCanilla) currentErrors.idCanilla = 'Seleccione un canillita.';
|
if (!idCanilla) currentErrors.idCanilla = 'Seleccione un canillita.';
|
||||||
@@ -200,7 +242,6 @@ const EntradaSalidaCanillaFormModal: React.FC<EntradaSalidaCanillaFormModalProps
|
|||||||
clearErrorMessage();
|
clearErrorMessage();
|
||||||
setModalSpecificApiError(null);
|
setModalSpecificApiError(null);
|
||||||
if (!validate()) return;
|
if (!validate()) return;
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
if (isEditing && initialData) {
|
if (isEditing && initialData) {
|
||||||
@@ -211,11 +252,15 @@ const EntradaSalidaCanillaFormModal: React.FC<EntradaSalidaCanillaFormModalProps
|
|||||||
cantEntrada: entradaNum,
|
cantEntrada: entradaNum,
|
||||||
observacion: editObservacion.trim() || undefined,
|
observacion: editObservacion.trim() || undefined,
|
||||||
};
|
};
|
||||||
|
// Aquí se llama al onSubmit que viene de la página padre (GestionarEntradasSalidasCanillaPage)
|
||||||
|
// para la lógica de actualización.
|
||||||
await onSubmit(dataToSubmitSingle, initialData.idParte);
|
await onSubmit(dataToSubmitSingle, initialData.idParte);
|
||||||
|
onClose(); // Cerrar el modal DESPUÉS de un submit de edición exitoso
|
||||||
} else {
|
} else {
|
||||||
|
// Lógica de creación BULK (se maneja internamente en el modal)
|
||||||
const itemsToSubmit: EntradaSalidaCanillaItemDto[] = items
|
const itemsToSubmit: EntradaSalidaCanillaItemDto[] = items
|
||||||
.filter(item =>
|
.filter(item =>
|
||||||
item.idPublicacion !== '' &&
|
item.idPublicacion && Number(item.idPublicacion) > 0 &&
|
||||||
((parseInt(item.cantSalida, 10) >= 0 && parseInt(item.cantEntrada, 10) >= 0) && (parseInt(item.cantSalida, 10) > 0 || parseInt(item.cantEntrada, 10) > 0) || item.observacion.trim() !== '')
|
((parseInt(item.cantSalida, 10) >= 0 && parseInt(item.cantEntrada, 10) >= 0) && (parseInt(item.cantSalida, 10) > 0 || parseInt(item.cantEntrada, 10) > 0) || item.observacion.trim() !== '')
|
||||||
)
|
)
|
||||||
.map(item => ({
|
.map(item => ({
|
||||||
@@ -226,7 +271,7 @@ const EntradaSalidaCanillaFormModal: React.FC<EntradaSalidaCanillaFormModalProps
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
if (itemsToSubmit.length === 0) {
|
if (itemsToSubmit.length === 0) {
|
||||||
setLocalErrors(prev => ({...prev, general: "No hay movimientos válidos para registrar. Asegúrese de seleccionar una publicación y/o ingresar cantidades."}));
|
setLocalErrors(prev => ({ ...prev, general: "No hay movimientos válidos para registrar..." }));
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -237,8 +282,9 @@ const EntradaSalidaCanillaFormModal: React.FC<EntradaSalidaCanillaFormModalProps
|
|||||||
items: itemsToSubmit,
|
items: itemsToSubmit,
|
||||||
};
|
};
|
||||||
await entradaSalidaCanillaService.createBulkEntradasSalidasCanilla(bulkData);
|
await entradaSalidaCanillaService.createBulkEntradasSalidasCanilla(bulkData);
|
||||||
|
onClose(); // Cerrar el modal DESPUÉS de un submit de creación bulk exitoso
|
||||||
}
|
}
|
||||||
onClose();
|
// onClose(); // Movido dentro de los bloques if/else para asegurar que solo se llama tras éxito
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error("Error en submit de EntradaSalidaCanillaFormModal:", error);
|
console.error("Error en submit de EntradaSalidaCanillaFormModal:", error);
|
||||||
if (axios.isAxiosError(error) && error.response) {
|
if (axios.isAxiosError(error) && error.response) {
|
||||||
@@ -246,7 +292,10 @@ const EntradaSalidaCanillaFormModal: React.FC<EntradaSalidaCanillaFormModalProps
|
|||||||
} else {
|
} else {
|
||||||
setModalSpecificApiError('Ocurrió un error inesperado.');
|
setModalSpecificApiError('Ocurrió un error inesperado.');
|
||||||
}
|
}
|
||||||
if (isEditing) throw error;
|
// NO llamar a onClose() aquí si hubo un error, para que el modal permanezca abierto
|
||||||
|
// y muestre el modalSpecificApiError.
|
||||||
|
// Si la edición (que usa el 'onSubmit' del padre) lanza un error, ese error se propagará
|
||||||
|
// al padre y el padre decidirá si el modal se cierra o no (actualmente no lo cierra).
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -300,7 +349,7 @@ const EntradaSalidaCanillaFormModal: React.FC<EntradaSalidaCanillaFormModalProps
|
|||||||
onChange={(e) => { setFecha(e.target.value); handleInputChange('fecha'); }}
|
onChange={(e) => { setFecha(e.target.value); handleInputChange('fecha'); }}
|
||||||
margin="dense" fullWidth error={!!localErrors.fecha} helperText={localErrors.fecha || ''}
|
margin="dense" fullWidth error={!!localErrors.fecha} helperText={localErrors.fecha || ''}
|
||||||
disabled={loading || isEditing} InputLabelProps={{ shrink: true }}
|
disabled={loading || isEditing} InputLabelProps={{ shrink: true }}
|
||||||
autoFocus={!isEditing}
|
autoFocus={!isEditing && !idCanilla} // AutoFocus si es nuevo y no hay canillita seleccionado
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{isEditing && initialData && (
|
{isEditing && initialData && (
|
||||||
@@ -328,47 +377,141 @@ const EntradaSalidaCanillaFormModal: React.FC<EntradaSalidaCanillaFormModalProps
|
|||||||
{!isEditing && (
|
{!isEditing && (
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="subtitle2" sx={{ mt: 1.5, mb: 0.5 }}>Movimientos por Publicación:</Typography>
|
<Typography variant="subtitle2" sx={{ mt: 1.5, mb: 0.5 }}>Movimientos por Publicación:</Typography>
|
||||||
{items.map((itemRow, index) => ( // item renombrado a itemRow
|
{/* Indicador de carga para los items */}
|
||||||
<Paper key={itemRow.id} elevation={1} sx={{ p: 1.5, mb: 1, position: 'relative' }}>
|
{loadingItems && <Box sx={{ display: 'flex', justifyContent: 'center', my: 1 }}><CircularProgress size={20} /></Box>}
|
||||||
{items.length > 1 && (
|
{!loadingItems && items.map((itemRow, index) => (
|
||||||
<IconButton onClick={() => handleRemoveRow(itemRow.id)} color="error" size="small"
|
<Paper
|
||||||
sx={{ position: 'absolute', top: 4, right: 4, zIndex:1 }}
|
key={itemRow.id}
|
||||||
aria-label="Quitar fila"
|
elevation={1}
|
||||||
|
sx={{
|
||||||
|
p: 1.5,
|
||||||
|
mb: 1,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<DeleteIcon fontSize="inherit" />
|
{/* Nivel 1: contenedor “padre” sin wrap */}
|
||||||
</IconButton>
|
<Box
|
||||||
)}
|
sx={{
|
||||||
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 1, flexWrap: 'wrap' }}>
|
display: 'flex',
|
||||||
<FormControl sx={{ minWidth: 160, flexBasis: 'calc(40% - 8px)', flexGrow:1 }} size="small" error={!!localErrors[`item_${itemRow.id}_idPublicacion`]}>
|
alignItems: 'center', // centra ícono + campos
|
||||||
<InputLabel required={parseInt(itemRow.cantSalida) > 0 || parseInt(itemRow.cantEntrada) > 0 || itemRow.observacion.trim() !== ''}>Pub. {index + 1}</InputLabel>
|
gap: 1,
|
||||||
<Select value={itemRow.idPublicacion} label={`Publicación ${index + 1}`}
|
// NOTA: aquí NO ponemos flexWrap, por defecto es 'nowrap'
|
||||||
onChange={(e) => handleItemChange(itemRow.id, 'idPublicacion', e.target.value as number)}
|
}}
|
||||||
|
>
|
||||||
|
{/* Nivel 2: contenedor que agrupa solo los campos y sí puede hacer wrap */}
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 1,
|
||||||
|
flexWrap: 'wrap', // los campos sí hacen wrap si no caben
|
||||||
|
flexGrow: 1, // ocupa todo el espacio disponible antes del ícono
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormControl
|
||||||
|
sx={{ minWidth: 160, flexBasis: 'calc(40% - 8px)', flexGrow: 1, minHeight: 0 }}
|
||||||
|
size="small"
|
||||||
|
error={!!localErrors[`item_${itemRow.id}_idPublicacion`]}
|
||||||
|
>
|
||||||
|
<InputLabel
|
||||||
|
required={
|
||||||
|
parseInt(itemRow.cantSalida) > 0 ||
|
||||||
|
parseInt(itemRow.cantEntrada) > 0 ||
|
||||||
|
itemRow.observacion.trim() !== ''
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Pub. {index + 1}
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
value={itemRow.idPublicacion}
|
||||||
|
label={`Publicación ${index + 1}`}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleItemChange(itemRow.id, 'idPublicacion', e.target.value as number)
|
||||||
|
}
|
||||||
disabled={loading || loadingDropdowns}
|
disabled={loading || loadingDropdowns}
|
||||||
|
sx={{ minWidth: 0 }} // permite que shrink si hace falta
|
||||||
>
|
>
|
||||||
<MenuItem value="" disabled><em>Seleccione</em></MenuItem>
|
<MenuItem value="" disabled>
|
||||||
|
<em>Seleccione</em>
|
||||||
|
</MenuItem>
|
||||||
{publicaciones.map((p) => (
|
{publicaciones.map((p) => (
|
||||||
<MenuItem key={p.idPublicacion} value={p.idPublicacion}>{p.nombre}</MenuItem>
|
<MenuItem key={p.idPublicacion} value={p.idPublicacion}>
|
||||||
|
{p.nombre}
|
||||||
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
{localErrors[`item_${itemRow.id}_idPublicacion`] && <FormHelperText>{localErrors[`item_${itemRow.id}_idPublicacion`]}</FormHelperText>}
|
{localErrors[`item_${itemRow.id}_idPublicacion`] && (
|
||||||
|
<FormHelperText>
|
||||||
|
{localErrors[`item_${itemRow.id}_idPublicacion`]}
|
||||||
|
</FormHelperText>
|
||||||
|
)}
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<TextField label="Llevados" type="number" size="small" value={itemRow.cantSalida}
|
|
||||||
|
<TextField
|
||||||
|
label="Llevados"
|
||||||
|
type="number"
|
||||||
|
size="small"
|
||||||
|
value={itemRow.cantSalida}
|
||||||
onChange={(e) => handleItemChange(itemRow.id, 'cantSalida', e.target.value)}
|
onChange={(e) => handleItemChange(itemRow.id, 'cantSalida', e.target.value)}
|
||||||
error={!!localErrors[`item_${itemRow.id}_cantSalida`]} helperText={localErrors[`item_${itemRow.id}_cantSalida`]}
|
error={!!localErrors[`item_${itemRow.id}_cantSalida`]}
|
||||||
inputProps={{ min: 0 }} sx={{ width: 'auto', flexBasis: 'calc(15% - 8px)', minWidth: '80px' }}
|
helperText={localErrors[`item_${itemRow.id}_cantSalida`]}
|
||||||
|
inputProps={{ min: 0 }}
|
||||||
|
sx={{
|
||||||
|
flexBasis: 'calc(15% - 8px)',
|
||||||
|
minWidth: '80px',
|
||||||
|
minHeight: 0,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<TextField label="Devueltos" type="number" size="small" value={itemRow.cantEntrada}
|
|
||||||
|
<TextField
|
||||||
|
label="Devueltos"
|
||||||
|
type="number"
|
||||||
|
size="small"
|
||||||
|
value={itemRow.cantEntrada}
|
||||||
onChange={(e) => handleItemChange(itemRow.id, 'cantEntrada', e.target.value)}
|
onChange={(e) => handleItemChange(itemRow.id, 'cantEntrada', e.target.value)}
|
||||||
error={!!localErrors[`item_${itemRow.id}_cantEntrada`]} helperText={localErrors[`item_${itemRow.id}_cantEntrada`]}
|
error={!!localErrors[`item_${itemRow.id}_cantEntrada`]}
|
||||||
inputProps={{ min: 0 }} sx={{ width: 'auto', flexBasis: 'calc(15% - 8px)', minWidth: '80px' }}
|
helperText={localErrors[`item_${itemRow.id}_cantEntrada`]}
|
||||||
|
inputProps={{ min: 0 }}
|
||||||
|
sx={{
|
||||||
|
flexBasis: 'calc(15% - 8px)',
|
||||||
|
minWidth: '80px',
|
||||||
|
minHeight: 0,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<TextField label="Obs." value={itemRow.observacion}
|
|
||||||
|
<TextField
|
||||||
|
label="Obs."
|
||||||
|
value={itemRow.observacion}
|
||||||
onChange={(e) => handleItemChange(itemRow.id, 'observacion', e.target.value)}
|
onChange={(e) => handleItemChange(itemRow.id, 'observacion', e.target.value)}
|
||||||
size="small" sx={{ flexGrow: 1, flexBasis: 'calc(25% - 8px)', minWidth: '120px' }} multiline maxRows={1}
|
size="small"
|
||||||
|
sx={{
|
||||||
|
flexGrow: 1,
|
||||||
|
flexBasis: 'calc(25% - 8px)',
|
||||||
|
minWidth: '120px',
|
||||||
|
minHeight: 0,
|
||||||
|
}}
|
||||||
|
multiline
|
||||||
|
maxRows={1}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* Ícono de eliminar: siempre en la misma línea */}
|
||||||
|
{items.length > 1 && (
|
||||||
|
<IconButton
|
||||||
|
onClick={() => handleRemoveRow(itemRow.id)}
|
||||||
|
color="error"
|
||||||
|
aria-label="Quitar fila"
|
||||||
|
sx={{
|
||||||
|
alignSelf: 'center', // mantén centrado verticalmente
|
||||||
|
// No necesita flexShrink, porque el padre no hace wrap
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DeleteIcon fontSize="medium" />
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{localErrors.general && <Alert severity="error" sx={{ mt: 1 }}>{localErrors.general}</Alert>}
|
{localErrors.general && <Alert severity="error" sx={{ mt: 1 }}>{localErrors.general}</Alert>}
|
||||||
<Button onClick={handleAddRow} startIcon={<AddIcon />} sx={{ mt: 1, alignSelf: 'flex-start' }} disabled={loading || loadingDropdowns || items.length >= publicaciones.length}>
|
<Button onClick={handleAddRow} startIcon={<AddIcon />} sx={{ mt: 1, alignSelf: 'flex-start' }} disabled={loading || loadingDropdowns || items.length >= publicaciones.length}>
|
||||||
Agregar Publicación
|
Agregar Publicación
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Modal, Box, Typography, Button, CircularProgress, Alert, FormGroup, FormControlLabel, Checkbox, Paper
|
||||||
|
} from '@mui/material';
|
||||||
|
import publicacionService from '../../../services/Distribucion/publicacionService';
|
||||||
|
import type { PublicacionDto } from '../../../models/dtos/Distribucion/PublicacionDto';
|
||||||
|
//import type { PublicacionDiaSemanaDto } from '../../../models/dtos/Distribucion/PublicacionDiaSemanaDto';
|
||||||
|
import type { UpdatePublicacionDiasSemanaRequestDto } from '../../../models/dtos/Distribucion/UpdatePublicacionDiasSemanaRequestDto';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const modalStyle = {
|
||||||
|
position: 'absolute' as 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
width: { xs: '90%', sm: '70%', md: '500px' },
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
border: '2px solid #000',
|
||||||
|
boxShadow: 24,
|
||||||
|
p: 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
const diasSemanaNombres = ["Domingo", "Lunes", "Martes", "Miércoles", "Jueves", "Viernes", "Sábado"];
|
||||||
|
|
||||||
|
interface PublicacionDiasSemanaModalProps {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
publicacion: PublicacionDto | null;
|
||||||
|
onConfigSaved: () => void; // Para recargar la lista de publicaciones si es necesario
|
||||||
|
}
|
||||||
|
|
||||||
|
const PublicacionDiasSemanaModal: React.FC<PublicacionDiasSemanaModalProps> = ({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
publicacion,
|
||||||
|
onConfigSaved
|
||||||
|
}) => {
|
||||||
|
const [selectedDays, setSelectedDays] = useState<Set<number>>(new Set());
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open && publicacion) {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
publicacionService.getConfiguracionDiasPublicacion(publicacion.idPublicacion)
|
||||||
|
.then(configs => {
|
||||||
|
const activeDays = new Set(configs.filter(c => c.activo).map(c => c.diaSemana));
|
||||||
|
setSelectedDays(activeDays);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error("Error al cargar configuración de días:", err);
|
||||||
|
setError("Error al cargar la configuración actual de días.");
|
||||||
|
})
|
||||||
|
.finally(() => setLoading(false));
|
||||||
|
} else {
|
||||||
|
setSelectedDays(new Set()); // Resetear al cerrar o si no hay publicación
|
||||||
|
}
|
||||||
|
}, [open, publicacion]);
|
||||||
|
|
||||||
|
const handleCheckboxChange = (dayIndex: number) => {
|
||||||
|
setSelectedDays(prev => {
|
||||||
|
const newSelection = new Set(prev);
|
||||||
|
if (newSelection.has(dayIndex)) {
|
||||||
|
newSelection.delete(dayIndex);
|
||||||
|
} else {
|
||||||
|
newSelection.add(dayIndex);
|
||||||
|
}
|
||||||
|
return newSelection;
|
||||||
|
});
|
||||||
|
setError(null); // Limpiar error al cambiar
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (!publicacion) return;
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
const requestDto: UpdatePublicacionDiasSemanaRequestDto = {
|
||||||
|
diasActivos: Array.from(selectedDays)
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
await publicacionService.updateConfiguracionDiasPublicacion(publicacion.idPublicacion, requestDto);
|
||||||
|
onConfigSaved(); // Notificar al padre que se guardó
|
||||||
|
onClose();
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error("Error al guardar configuración de días:", err);
|
||||||
|
const message = axios.isAxiosError(err) && err.response?.data?.message
|
||||||
|
? err.response.data.message
|
||||||
|
: 'Error al guardar la configuración.';
|
||||||
|
setError(message);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!publicacion) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal open={open} onClose={onClose}>
|
||||||
|
<Box sx={modalStyle}>
|
||||||
|
<Typography variant="h6" gutterBottom>
|
||||||
|
Configurar Días de Salida para: {publicacion.nombre}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" color="text.secondary" sx={{mb:2}}>
|
||||||
|
Marque los días en que esta publicación debe aparecer por defecto en los movimientos de canillitas.
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
{loading && <Box sx={{display:'flex', justifyContent:'center', my:2}}><CircularProgress /></Box>}
|
||||||
|
{error && <Alert severity="error" sx={{ mb: 2 }}>{error}</Alert>}
|
||||||
|
|
||||||
|
{!loading && (
|
||||||
|
<Paper variant="outlined" sx={{p:2}}>
|
||||||
|
<FormGroup>
|
||||||
|
{diasSemanaNombres.map((nombreDia, index) => (
|
||||||
|
<FormControlLabel
|
||||||
|
key={index}
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={selectedDays.has(index)}
|
||||||
|
onChange={() => handleCheckboxChange(index)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={nombreDia}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</FormGroup>
|
||||||
|
</Paper>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
|
||||||
|
<Button onClick={onClose} color="secondary" disabled={loading}>Cancelar</Button>
|
||||||
|
<Button onClick={handleSubmit} variant="contained" disabled={loading}>
|
||||||
|
{loading ? <CircularProgress size={24} /> : 'Guardar Configuración'}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PublicacionDiasSemanaModal;
|
||||||
@@ -9,6 +9,7 @@ export interface UserContextData {
|
|||||||
nombreCompleto: string;
|
nombreCompleto: string;
|
||||||
esSuperAdmin: boolean;
|
esSuperAdmin: boolean;
|
||||||
debeCambiarClave: boolean;
|
debeCambiarClave: boolean;
|
||||||
|
perfil: string;
|
||||||
idPerfil: number;
|
idPerfil: number;
|
||||||
permissions: string[]; // Guardamos los codAcc
|
permissions: string[]; // Guardamos los codAcc
|
||||||
}
|
}
|
||||||
@@ -20,6 +21,7 @@ interface DecodedJwtPayload {
|
|||||||
given_name?: string; // Nombre (estándar, pero verifica tu token)
|
given_name?: string; // Nombre (estándar, pero verifica tu token)
|
||||||
family_name?: string; // Apellido (estándar, pero verifica tu token)
|
family_name?: string; // Apellido (estándar, pero verifica tu token)
|
||||||
role: string | string[]; // Puede ser uno o varios roles
|
role: string | string[]; // Puede ser uno o varios roles
|
||||||
|
perfil: string;
|
||||||
idPerfil: string; // (viene como string)
|
idPerfil: string; // (viene como string)
|
||||||
debeCambiarClave: string; // (viene como string "True" o "False")
|
debeCambiarClave: string; // (viene como string "True" o "False")
|
||||||
permission?: string | string[]; // Nuestros claims de permiso (codAcc)
|
permission?: string | string[]; // Nuestros claims de permiso (codAcc)
|
||||||
@@ -74,6 +76,7 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
|||||||
debeCambiarClave: decodedToken.debeCambiarClave?.toLowerCase() === 'true',
|
debeCambiarClave: decodedToken.debeCambiarClave?.toLowerCase() === 'true',
|
||||||
idPerfil: decodedToken.idPerfil ? parseInt(decodedToken.idPerfil, 10) : 0,
|
idPerfil: decodedToken.idPerfil ? parseInt(decodedToken.idPerfil, 10) : 0,
|
||||||
permissions: permissions,
|
permissions: permissions,
|
||||||
|
perfil: decodedToken.perfil || 'Usuario' // Asignar un valor por defecto si no existe
|
||||||
};
|
};
|
||||||
|
|
||||||
setToken(jwtToken);
|
setToken(jwtToken);
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
import React, { type ReactNode, useState, useEffect } from 'react';
|
import React, { type ReactNode, useState, useEffect } from 'react';
|
||||||
import { Box, AppBar, Toolbar, Typography, Button, Tabs, Tab, Paper } from '@mui/material';
|
import {
|
||||||
|
Box, AppBar, Toolbar, Typography, Tabs, Tab, Paper,
|
||||||
|
IconButton, Menu, MenuItem, ListItemIcon, ListItemText, Divider // Nuevas importaciones
|
||||||
|
} from '@mui/material';
|
||||||
|
import AccountCircle from '@mui/icons-material/AccountCircle'; // Icono de usuario
|
||||||
|
import LockResetIcon from '@mui/icons-material/LockReset'; // Icono para cambiar contraseña
|
||||||
|
import LogoutIcon from '@mui/icons-material/Logout'; // Icono para cerrar sesión
|
||||||
import { useAuth } from '../contexts/AuthContext';
|
import { useAuth } from '../contexts/AuthContext';
|
||||||
import ChangePasswordModal from '../components/Modals/Usuarios/ChangePasswordModal';
|
import ChangePasswordModal from '../components/Modals/Usuarios/ChangePasswordModal';
|
||||||
import { useNavigate, useLocation } from 'react-router-dom'; // Para manejar la navegación y la ruta actual
|
import { useNavigate, useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
interface MainLayoutProps {
|
interface MainLayoutProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
@@ -18,12 +24,10 @@ const modules = [
|
|||||||
{ label: 'Usuarios', path: '/usuarios' },
|
{ label: 'Usuarios', path: '/usuarios' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
|
const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
|
||||||
const {
|
const {
|
||||||
user,
|
user,
|
||||||
logout,
|
logout,
|
||||||
// ... (resto de las props de useAuth) ...
|
|
||||||
isAuthenticated,
|
isAuthenticated,
|
||||||
isPasswordChangeForced,
|
isPasswordChangeForced,
|
||||||
showForcedPasswordChangeModal,
|
showForcedPasswordChangeModal,
|
||||||
@@ -32,9 +36,10 @@ const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
|
|||||||
} = useAuth();
|
} = useAuth();
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation(); // Para obtener la ruta actual
|
const location = useLocation();
|
||||||
|
|
||||||
const [selectedTab, setSelectedTab] = useState<number | false>(false);
|
const [selectedTab, setSelectedTab] = useState<number | false>(false);
|
||||||
|
const [anchorElUserMenu, setAnchorElUserMenu] = useState<null | HTMLElement>(null); // Estado para el menú de usuario
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const currentModulePath = modules.findIndex(module =>
|
const currentModulePath = modules.findIndex(module =>
|
||||||
@@ -43,12 +48,30 @@ const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
|
|||||||
if (currentModulePath !== -1) {
|
if (currentModulePath !== -1) {
|
||||||
setSelectedTab(currentModulePath);
|
setSelectedTab(currentModulePath);
|
||||||
} else if (location.pathname === '/') {
|
} else if (location.pathname === '/') {
|
||||||
setSelectedTab(0);
|
setSelectedTab(0); // Asegurar que la pestaña de Inicio se seleccione para la ruta raíz
|
||||||
} else {
|
} else {
|
||||||
setSelectedTab(false);
|
setSelectedTab(false); // Ninguna pestaña seleccionada si no coincide
|
||||||
}
|
}
|
||||||
}, [location.pathname]);
|
}, [location.pathname]);
|
||||||
|
|
||||||
|
const handleOpenUserMenu = (event: React.MouseEvent<HTMLElement>) => {
|
||||||
|
setAnchorElUserMenu(event.currentTarget);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseUserMenu = () => {
|
||||||
|
setAnchorElUserMenu(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChangePasswordClick = () => {
|
||||||
|
setShowForcedPasswordChangeModal(true);
|
||||||
|
handleCloseUserMenu();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLogoutClick = () => {
|
||||||
|
logout();
|
||||||
|
handleCloseUserMenu(); // Cierra el menú antes de desloguear completamente
|
||||||
|
};
|
||||||
|
|
||||||
const handleModalClose = (passwordChangedSuccessfully: boolean) => {
|
const handleModalClose = (passwordChangedSuccessfully: boolean) => {
|
||||||
if (passwordChangedSuccessfully) {
|
if (passwordChangedSuccessfully) {
|
||||||
passwordChangeCompleted();
|
passwordChangeCompleted();
|
||||||
@@ -70,7 +93,6 @@ const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
|
|||||||
const isReportesModule = location.pathname.startsWith('/reportes');
|
const isReportesModule = location.pathname.startsWith('/reportes');
|
||||||
|
|
||||||
if (showForcedPasswordChangeModal && isPasswordChangeForced) {
|
if (showForcedPasswordChangeModal && isPasswordChangeForced) {
|
||||||
// ... (lógica del modal forzado sin cambios) ...
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '100vh' }}>
|
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '100vh' }}>
|
||||||
<ChangePasswordModal
|
<ChangePasswordModal
|
||||||
@@ -84,33 +106,94 @@ const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', minHeight: '100vh' }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', minHeight: '100vh' }}>
|
||||||
<AppBar position="static">
|
<AppBar position="sticky" elevation={1} /* Elevation sutil para AppBar */>
|
||||||
{/* ... (Toolbar y Tabs sin cambios) ... */}
|
<Toolbar sx={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
<Toolbar>
|
<Typography variant="h6" component="div" noWrap sx={{ cursor: 'pointer' }} onClick={() => navigate('/')}>
|
||||||
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
|
|
||||||
Sistema de Gestión - El Día
|
Sistema de Gestión - El Día
|
||||||
</Typography>
|
</Typography>
|
||||||
{user && <Typography sx={{ mr: 2 }}>Hola, {user.nombreCompleto}</Typography>}
|
|
||||||
{isAuthenticated && !isPasswordChangeForced && (
|
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||||
<Button
|
{user && (
|
||||||
color="inherit"
|
<Typography sx={{ mr: 2, display: { xs: 'none', sm: 'block' } }} /* Ocultar en pantallas muy pequeñas */>
|
||||||
onClick={() => setShowForcedPasswordChangeModal(true)}
|
Hola, {user.nombreCompleto}
|
||||||
>
|
</Typography>
|
||||||
Cambiar Contraseña
|
|
||||||
</Button>
|
|
||||||
)}
|
)}
|
||||||
<Button color="inherit" onClick={logout}>Cerrar Sesión</Button>
|
{isAuthenticated && (
|
||||||
|
<>
|
||||||
|
<IconButton
|
||||||
|
size="large"
|
||||||
|
aria-label="Cuenta del usuario"
|
||||||
|
aria-controls="menu-appbar"
|
||||||
|
aria-haspopup="true"
|
||||||
|
sx={{
|
||||||
|
padding: '15px',
|
||||||
|
}}
|
||||||
|
onClick={handleOpenUserMenu}
|
||||||
|
color="inherit"
|
||||||
|
>
|
||||||
|
<AccountCircle sx={{ fontSize: 36 }} />
|
||||||
|
</IconButton>
|
||||||
|
<Menu
|
||||||
|
id="menu-appbar"
|
||||||
|
anchorEl={anchorElUserMenu}
|
||||||
|
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
|
||||||
|
keepMounted
|
||||||
|
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
|
||||||
|
open={Boolean(anchorElUserMenu)}
|
||||||
|
onClose={handleCloseUserMenu}
|
||||||
|
sx={{ '& .MuiPaper-root': { minWidth: 220, marginTop: '8px' } }}
|
||||||
|
>
|
||||||
|
{user && ( // Mostrar info del usuario en el menú
|
||||||
|
<Box sx={{ px: 2, py: 1.5, pointerEvents: 'none' /* Para que no sea clickeable */ }}>
|
||||||
|
<Typography variant="subtitle1" sx={{ fontWeight: 'medium' }}>{user.nombreCompleto}</Typography>
|
||||||
|
<Typography variant="body2" color="text.secondary">{user.username}</Typography>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
{user && <Divider sx={{ mb: 1 }} />}
|
||||||
|
|
||||||
|
{!isPasswordChangeForced && ( // No mostrar si ya está forzado a cambiarla
|
||||||
|
<MenuItem onClick={handleChangePasswordClick}>
|
||||||
|
<ListItemIcon><LockResetIcon fontSize="small" /></ListItemIcon>
|
||||||
|
<ListItemText>Cambiar Contraseña</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
<MenuItem onClick={handleLogoutClick}>
|
||||||
|
<ListItemIcon><LogoutIcon fontSize="small" /></ListItemIcon>
|
||||||
|
<ListItemText>Cerrar Sesión</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
</Menu>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
<Paper square elevation={0} >
|
<Paper square elevation={0} >
|
||||||
<Tabs
|
<Tabs
|
||||||
value={selectedTab}
|
value={selectedTab}
|
||||||
onChange={handleTabChange}
|
onChange={handleTabChange}
|
||||||
indicatorColor="secondary"
|
indicatorColor="secondary" // O 'primary' si prefieres el mismo color que el fondo
|
||||||
textColor="inherit"
|
textColor="inherit" // El texto de la pestaña hereda el color (blanco sobre fondo oscuro)
|
||||||
variant="scrollable"
|
variant="scrollable"
|
||||||
scrollButtons="auto"
|
scrollButtons="auto"
|
||||||
|
allowScrollButtonsMobile
|
||||||
aria-label="módulos principales"
|
aria-label="módulos principales"
|
||||||
sx={{ backgroundColor: 'primary.main', color: 'white' }}
|
sx={{
|
||||||
|
backgroundColor: 'primary.main', // Color de fondo de las pestañas
|
||||||
|
color: 'white', // Color del texto de las pestañas
|
||||||
|
'& .MuiTabs-indicator': {
|
||||||
|
height: 3, // Un indicador un poco más grueso
|
||||||
|
},
|
||||||
|
'& .MuiTab-root': { // Estilo para cada pestaña
|
||||||
|
minWidth: 100, // Ancho mínimo para cada pestaña
|
||||||
|
textTransform: 'none', // Evitar MAYÚSCULAS por defecto
|
||||||
|
fontWeight: 'normal',
|
||||||
|
opacity: 0.85, // Ligeramente transparentes si no están seleccionadas
|
||||||
|
'&.Mui-selected': {
|
||||||
|
fontWeight: 'bold',
|
||||||
|
opacity: 1,
|
||||||
|
// color: 'secondary.main' // Opcional: color diferente para la pestaña seleccionada
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{modules.map((module) => (
|
{modules.map((module) => (
|
||||||
<Tab key={module.path} label={module.label} />
|
<Tab key={module.path} label={module.label} />
|
||||||
@@ -119,30 +202,30 @@ const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
|
|||||||
</Paper>
|
</Paper>
|
||||||
</AppBar>
|
</AppBar>
|
||||||
|
|
||||||
{/* Contenido del Módulo */}
|
|
||||||
<Box
|
<Box
|
||||||
component="main"
|
component="main"
|
||||||
sx={{
|
sx={{
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
py: isReportesModule ? 0 : 3, // Padding vertical condicional. Si es el módulo de Reportes, px es 0 si no 3
|
py: isReportesModule ? 0 : { xs: 1.5, sm: 2, md: 2.5 }, // Padding vertical responsivo
|
||||||
px: isReportesModule ? 0 : 3, // Padding horizontal condicional. Si es el módulo de Reportes, px es 0 si no 3
|
px: isReportesModule ? 0 : { xs: 1.5, sm: 2, md: 2.5 }, // Padding horizontal responsivo
|
||||||
display: 'flex', // IMPORTANTE: Para que el hijo (ReportesIndexPage) pueda usar height: '100%'
|
display: 'flex',
|
||||||
flexDirection: 'column' // IMPORTANTE
|
flexDirection: 'column'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box component="footer" sx={{ p: 1, mt: 'auto', backgroundColor: 'primary.dark', color: 'white', textAlign: 'center' }}>
|
<Box component="footer" sx={{ p: 1, backgroundColor: 'grey.200' /* Un gris más claro */, color: 'text.secondary', textAlign: 'left', borderTop: (theme) => `1px solid ${theme.palette.divider}` }}>
|
||||||
<Typography variant="body2">
|
<Typography variant="caption">
|
||||||
Usuario: {user?.username} | Acceso: {user?.esSuperAdmin ? 'Super Admin' : `Perfil ID ${user?.userId}`} | Versión: {/* TODO: Obtener versión */}
|
{/* Puedes usar caption para un texto más pequeño en el footer */}
|
||||||
|
Usuario: {user?.username} | Acceso: {user?.esSuperAdmin ? 'Super Administrador' : (user?.perfil || `ID ${user?.idPerfil}`)}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<ChangePasswordModal
|
<ChangePasswordModal
|
||||||
open={showForcedPasswordChangeModal}
|
open={showForcedPasswordChangeModal && !isPasswordChangeForced} // Solo mostrar si no es el forzado inicial
|
||||||
onClose={handleModalClose}
|
onClose={() => handleModalClose(false)} // Asumir que si se cierra sin cambiar, no fue exitoso
|
||||||
isFirstLogin={isPasswordChangeForced}
|
isFirstLogin={false} // Este modal no es para el primer login forzado
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export interface PublicacionDiaSemanaDto {
|
||||||
|
idPublicacionDia: number;
|
||||||
|
idPublicacion: number;
|
||||||
|
diaSemana: number; // 0 (Domingo) a 6 (Sábado)
|
||||||
|
activo: boolean;
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export interface UpdatePublicacionDiasSemanaRequestDto {
|
||||||
|
diasActivos: number[]; // Array de números de día (0-6)
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export interface LiquidacionCanillaDetalleDto {
|
||||||
|
publicacion: string;
|
||||||
|
canilla: string; // Nombre del canilla
|
||||||
|
totalCantSalida: number;
|
||||||
|
totalCantEntrada: number;
|
||||||
|
totalRendir: number;
|
||||||
|
precioEjemplar: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export interface LiquidacionCanillaGananciaDto {
|
||||||
|
publicacion: string;
|
||||||
|
totalRendir: number; // Este es el monto de la comisión/ganancia
|
||||||
|
}
|
||||||
@@ -17,22 +17,36 @@ const ContablesIndexPage: React.FC = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const currentBasePath = '/contables';
|
const currentBasePath = '/contables';
|
||||||
const subPath = location.pathname.startsWith(currentBasePath + '/')
|
const defaultSubPath = 'pagos-distribuidores'; // Define tu sub-ruta por defecto aquí
|
||||||
? location.pathname.substring(currentBasePath.length + 1)
|
|
||||||
: (location.pathname === currentBasePath ? contablesSubModules[0]?.path : undefined);
|
|
||||||
|
|
||||||
const activeTabIndex = contablesSubModules.findIndex(
|
const pathParts = location.pathname.split('/');
|
||||||
(subModule) => subModule.path === subPath
|
const currentSubPathSegment = pathParts[2]; // /contables -> pathParts[1] es 'contables', pathParts[2] sería la sub-ruta
|
||||||
|
|
||||||
|
let activeTabIndex = -1;
|
||||||
|
|
||||||
|
if (currentSubPathSegment) {
|
||||||
|
activeTabIndex = contablesSubModules.findIndex(
|
||||||
|
(subModule) => subModule.path === currentSubPathSegment
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (activeTabIndex !== -1) {
|
if (activeTabIndex !== -1) {
|
||||||
setSelectedSubTab(activeTabIndex);
|
setSelectedSubTab(activeTabIndex);
|
||||||
} else {
|
} else {
|
||||||
if (location.pathname === currentBasePath && contablesSubModules.length > 0) {
|
// Si estamos en la ruta base /contables o una subruta no reconocida
|
||||||
navigate(contablesSubModules[0].path, { replace: true });
|
if (location.pathname === currentBasePath || (location.pathname.startsWith(currentBasePath) && activeTabIndex === -1) ) {
|
||||||
|
const defaultTabIndex = contablesSubModules.findIndex(sm => sm.path === defaultSubPath);
|
||||||
|
if (defaultTabIndex !== -1) {
|
||||||
|
navigate(`${currentBasePath}/${defaultSubPath}`, { replace: true });
|
||||||
|
setSelectedSubTab(defaultTabIndex);
|
||||||
|
} else if (contablesSubModules.length > 0) { // Fallback al primero si el default no existe
|
||||||
|
navigate(`${currentBasePath}/${contablesSubModules[0].path}`, { replace: true });
|
||||||
setSelectedSubTab(0);
|
setSelectedSubTab(0);
|
||||||
} else {
|
} else {
|
||||||
setSelectedSubTab(false);
|
setSelectedSubTab(false); // No hay sub-módulos
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setSelectedSubTab(false); // No es una ruta del módulo contable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [location.pathname, navigate]);
|
}, [location.pathname, navigate]);
|
||||||
|
|||||||
@@ -154,8 +154,8 @@ const GestionarNotasCDPage: React.FC = () => {
|
|||||||
if (!loading && !puedeVer && !loadingFiltersDropdown) return <Box sx={{ p: 2 }}><Alert severity="error">{error || "Acceso denegado."}</Alert></Box>;
|
if (!loading && !puedeVer && !loadingFiltersDropdown) return <Box sx={{ p: 2 }}><Alert severity="error">{error || "Acceso denegado."}</Alert></Box>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 2 }}>
|
<Box sx={{ p: 1 }}>
|
||||||
<Typography variant="h4" gutterBottom>Notas de Crédito/Débito</Typography>
|
<Typography variant="h5" gutterBottom>Notas de Crédito/Débito</Typography>
|
||||||
<Paper sx={{ p: 2, mb: 2 }}>
|
<Paper sx={{ p: 2, mb: 2 }}>
|
||||||
<Typography variant="h6" gutterBottom>Filtros <FilterListIcon fontSize="small"/></Typography>
|
<Typography variant="h6" gutterBottom>Filtros <FilterListIcon fontSize="small"/></Typography>
|
||||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, alignItems: 'center', mb: 2}}>
|
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, alignItems: 'center', mb: 2}}>
|
||||||
|
|||||||
@@ -133,8 +133,8 @@ const GestionarPagosDistribuidorPage: React.FC = () => {
|
|||||||
if (!loading && !puedeVer && !loadingFiltersDropdown) return <Box sx={{ p: 2 }}><Alert severity="error">{error || "Acceso denegado."}</Alert></Box>;
|
if (!loading && !puedeVer && !loadingFiltersDropdown) return <Box sx={{ p: 2 }}><Alert severity="error">{error || "Acceso denegado."}</Alert></Box>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 2 }}>
|
<Box sx={{ p: 1 }}>
|
||||||
<Typography variant="h4" gutterBottom>Pagos de Distribuidores</Typography>
|
<Typography variant="h5" gutterBottom>Pagos de Distribuidores</Typography>
|
||||||
<Paper sx={{ p: 2, mb: 2 }}>
|
<Paper sx={{ p: 2, mb: 2 }}>
|
||||||
<Typography variant="h6" gutterBottom>Filtros <FilterListIcon fontSize="small"/></Typography>
|
<Typography variant="h6" gutterBottom>Filtros <FilterListIcon fontSize="small"/></Typography>
|
||||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, alignItems: 'center', mb: 2}}>
|
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, alignItems: 'center', mb: 2}}>
|
||||||
|
|||||||
@@ -3,10 +3,14 @@ import React, { useState, useEffect, useCallback } from 'react';
|
|||||||
import {
|
import {
|
||||||
Box, Typography, TextField, Button, Paper, IconButton, Menu, MenuItem,
|
Box, Typography, TextField, Button, Paper, IconButton, Menu, MenuItem,
|
||||||
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TablePagination,
|
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TablePagination,
|
||||||
CircularProgress, Alert
|
CircularProgress, Alert,
|
||||||
|
ListItemIcon,
|
||||||
|
ListItemText
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
import AddIcon from '@mui/icons-material/Add'; // Icono para agregar
|
||||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
import MoreVertIcon from '@mui/icons-material/MoreVert'; // Icono para más opciones
|
||||||
|
import EditIcon from '@mui/icons-material/Edit'; // Icono para modificar
|
||||||
|
import DeleteIcon from '@mui/icons-material/Delete'; // Icono para eliminar
|
||||||
import tipoPagoService from '../../services/Contables/tipoPagoService';
|
import tipoPagoService from '../../services/Contables/tipoPagoService';
|
||||||
import type { TipoPago } from '../../models/Entities/TipoPago';
|
import type { TipoPago } from '../../models/Entities/TipoPago';
|
||||||
import type { CreateTipoPagoDto } from '../../models/dtos/Contables/CreateTipoPagoDto';
|
import type { CreateTipoPagoDto } from '../../models/dtos/Contables/CreateTipoPagoDto';
|
||||||
@@ -129,8 +133,8 @@ const GestionarTiposPagoPage: React.FC = () => {
|
|||||||
const displayData = tiposPago.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage);
|
const displayData = tiposPago.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 2 }}>
|
<Box sx={{ p: 1 }}>
|
||||||
<Typography variant="h4" gutterBottom>
|
<Typography variant="h5" gutterBottom>
|
||||||
Gestionar Tipos de Pago
|
Gestionar Tipos de Pago
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
@@ -148,7 +152,6 @@ const GestionarTiposPagoPage: React.FC = () => {
|
|||||||
{/* <Button variant="contained" onClick={cargarTiposPago}>Buscar</Button> */}
|
{/* <Button variant="contained" onClick={cargarTiposPago}>Buscar</Button> */}
|
||||||
</Box>
|
</Box>
|
||||||
{puedeCrear && (
|
{puedeCrear && (
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'flex-end', mb: 2 }}>
|
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
startIcon={<AddIcon />}
|
startIcon={<AddIcon />}
|
||||||
@@ -157,7 +160,6 @@ const GestionarTiposPagoPage: React.FC = () => {
|
|||||||
>
|
>
|
||||||
Agregar Nuevo Tipo
|
Agregar Nuevo Tipo
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
|
||||||
)}
|
)}
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
@@ -217,12 +219,14 @@ const GestionarTiposPagoPage: React.FC = () => {
|
|||||||
>
|
>
|
||||||
{puedeModificar && (
|
{puedeModificar && (
|
||||||
<MenuItem onClick={() => { handleOpenModal(selectedTipoPagoRow!); handleMenuClose(); }}>
|
<MenuItem onClick={() => { handleOpenModal(selectedTipoPagoRow!); handleMenuClose(); }}>
|
||||||
Modificar
|
<ListItemIcon><EditIcon fontSize="small" /></ListItemIcon>
|
||||||
|
<ListItemText>Modificar</ListItemText>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
{puedeEliminar && (
|
{puedeEliminar && (
|
||||||
<MenuItem onClick={() => handleDelete(selectedTipoPagoRow!.idTipoPago)}>
|
<MenuItem onClick={() => handleDelete(selectedTipoPagoRow!.idTipoPago)}>
|
||||||
Eliminar
|
<ListItemIcon><DeleteIcon fontSize="small" /></ListItemIcon>
|
||||||
|
<ListItemText>Eliminar</ListItemText>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
{/* Si no tiene ningún permiso, el menú podría estar vacío o no mostrarse */}
|
{/* Si no tiene ningún permiso, el menú podría estar vacío o no mostrarse */}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import AddIcon from '@mui/icons-material/Add';
|
|||||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||||
import ToggleOnIcon from '@mui/icons-material/ToggleOn';
|
import ToggleOnIcon from '@mui/icons-material/ToggleOn';
|
||||||
import ToggleOffIcon from '@mui/icons-material/ToggleOff';
|
import ToggleOffIcon from '@mui/icons-material/ToggleOff';
|
||||||
|
import EditIcon from '@mui/icons-material/Edit';
|
||||||
import canillaService from '../../services/Distribucion/canillaService';
|
import canillaService from '../../services/Distribucion/canillaService';
|
||||||
import type { CanillaDto } from '../../models/dtos/Distribucion/CanillaDto';
|
import type { CanillaDto } from '../../models/dtos/Distribucion/CanillaDto';
|
||||||
import type { CreateCanillaDto } from '../../models/dtos/Distribucion/CreateCanillaDto';
|
import type { CreateCanillaDto } from '../../models/dtos/Distribucion/CreateCanillaDto';
|
||||||
@@ -121,8 +122,8 @@ const GestionarCanillitasPage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 2 }}>
|
<Box sx={{ p: 1 }}>
|
||||||
<Typography variant="h4" gutterBottom>Gestionar Canillitas</Typography>
|
<Typography variant="h5" gutterBottom>Gestionar Canillitas</Typography>
|
||||||
<Paper sx={{ p: 2, mb: 2 }}>
|
<Paper sx={{ p: 2, mb: 2 }}>
|
||||||
<Box sx={{ display: 'flex', gap: 2, mb: 2, flexWrap: 'wrap', alignItems: 'center' }}>
|
<Box sx={{ display: 'flex', gap: 2, mb: 2, flexWrap: 'wrap', alignItems: 'center' }}>
|
||||||
<TextField
|
<TextField
|
||||||
@@ -156,9 +157,7 @@ const GestionarCanillitasPage: React.FC = () => {
|
|||||||
{/* <Button variant="contained" onClick={cargarCanillitas} size="small">Buscar</Button> */}
|
{/* <Button variant="contained" onClick={cargarCanillitas} size="small">Buscar</Button> */}
|
||||||
</Box>
|
</Box>
|
||||||
{puedeCrear && (
|
{puedeCrear && (
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'flex-end', mb: 2 }}>
|
|
||||||
<Button variant="contained" startIcon={<AddIcon />} onClick={() => handleOpenModal()} sx={{ mb: 2 }}>Agregar Canillita</Button>
|
<Button variant="contained" startIcon={<AddIcon />} onClick={() => handleOpenModal()} sx={{ mb: 2 }}>Agregar Canillita</Button>
|
||||||
</Box>
|
|
||||||
)}
|
)}
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
@@ -203,7 +202,7 @@ const GestionarCanillitasPage: React.FC = () => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<Menu anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleMenuClose}>
|
<Menu anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleMenuClose}>
|
||||||
{puedeModificar && (<MenuItem onClick={() => { handleOpenModal(selectedCanillitaRow!); handleMenuClose(); }}>Modificar</MenuItem>)}
|
{puedeModificar && (<MenuItem onClick={() => { handleOpenModal(selectedCanillitaRow!); handleMenuClose(); }}><EditIcon fontSize="small" sx={{ mr: 1 }} /> Modificar</MenuItem>)}
|
||||||
{puedeDarBaja && selectedCanillitaRow && (
|
{puedeDarBaja && selectedCanillitaRow && (
|
||||||
<MenuItem onClick={() => handleToggleBaja(selectedCanillitaRow)}>
|
<MenuItem onClick={() => handleToggleBaja(selectedCanillitaRow)}>
|
||||||
{selectedCanillitaRow.baja ? <ToggleOnIcon sx={{mr:1}}/> : <ToggleOffIcon sx={{mr:1}}/>}
|
{selectedCanillitaRow.baja ? <ToggleOnIcon sx={{mr:1}}/> : <ToggleOffIcon sx={{mr:1}}/>}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import DeleteIcon from '@mui/icons-material/Delete';
|
|||||||
import FilterListIcon from '@mui/icons-material/FilterList';
|
import FilterListIcon from '@mui/icons-material/FilterList';
|
||||||
|
|
||||||
import controlDevolucionesService from '../../services/Distribucion/controlDevolucionesService';
|
import controlDevolucionesService from '../../services/Distribucion/controlDevolucionesService';
|
||||||
import empresaService from '../../services/Distribucion/empresaService'; // Para el filtro de empresa
|
import empresaService from '../../services/Distribucion/empresaService';
|
||||||
|
|
||||||
import type { ControlDevolucionesDto } from '../../models/dtos/Distribucion/ControlDevolucionesDto';
|
import type { ControlDevolucionesDto } from '../../models/dtos/Distribucion/ControlDevolucionesDto';
|
||||||
import type { CreateControlDevolucionesDto } from '../../models/dtos/Distribucion/CreateControlDevolucionesDto';
|
import type { CreateControlDevolucionesDto } from '../../models/dtos/Distribucion/CreateControlDevolucionesDto';
|
||||||
@@ -28,9 +28,8 @@ const GestionarControlDevolucionesPage: React.FC = () => {
|
|||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [apiErrorMessage, setApiErrorMessage] = useState<string | null>(null);
|
const [apiErrorMessage, setApiErrorMessage] = useState<string | null>(null);
|
||||||
|
|
||||||
// Filtros
|
const [filtroFechaDesde, setFiltroFechaDesde] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||||
const [filtroFechaDesde, setFiltroFechaDesde] = useState<string>(new Date().toISOString().split('T')[0]); //useState('');
|
const [filtroFechaHasta, setFiltroFechaHasta] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||||
const [filtroFechaHasta, setFiltroFechaHasta] = useState<string>(new Date().toISOString().split('T')[0]); //useState('');
|
|
||||||
const [filtroIdEmpresa, setFiltroIdEmpresa] = useState<number | string>('');
|
const [filtroIdEmpresa, setFiltroIdEmpresa] = useState<number | string>('');
|
||||||
|
|
||||||
const [empresas, setEmpresas] = useState<EmpresaDto[]>([]);
|
const [empresas, setEmpresas] = useState<EmpresaDto[]>([]);
|
||||||
@@ -45,11 +44,24 @@ const GestionarControlDevolucionesPage: React.FC = () => {
|
|||||||
const [selectedRow, setSelectedRow] = useState<ControlDevolucionesDto | null>(null);
|
const [selectedRow, setSelectedRow] = useState<ControlDevolucionesDto | null>(null);
|
||||||
|
|
||||||
const { tienePermiso, isSuperAdmin } = usePermissions();
|
const { tienePermiso, isSuperAdmin } = usePermissions();
|
||||||
// Permisos CD001 (Ver), CD002 (Crear), CD003 (Modificar)
|
|
||||||
const puedeVer = isSuperAdmin || tienePermiso("CD001");
|
const puedeVer = isSuperAdmin || tienePermiso("CD001");
|
||||||
const puedeCrear = isSuperAdmin || tienePermiso("CD002");
|
const puedeCrear = isSuperAdmin || tienePermiso("CD002");
|
||||||
const puedeModificar = isSuperAdmin || tienePermiso("CD003");
|
const puedeModificar = isSuperAdmin || tienePermiso("CD003");
|
||||||
const puedeEliminar = isSuperAdmin || tienePermiso("CD003"); // Asumiendo que modificar incluye eliminar
|
const puedeEliminar = isSuperAdmin || tienePermiso("CD003");
|
||||||
|
|
||||||
|
// CORREGIDO: Función para formatear la fecha
|
||||||
|
const formatDate = (dateString?: string | null): string => {
|
||||||
|
if (!dateString) return '-';
|
||||||
|
// Asumimos que dateString viene del backend como "YYYY-MM-DD" o "YYYY-MM-DDTHH:mm:ss..."
|
||||||
|
const datePart = dateString.split('T')[0]; // Tomar solo la parte YYYY-MM-DD
|
||||||
|
const parts = datePart.split('-');
|
||||||
|
if (parts.length === 3) {
|
||||||
|
// parts[0] = YYYY, parts[1] = MM, parts[2] = DD
|
||||||
|
return `${parts[2]}/${parts[1]}/${parts[0]}`; // Formato DD/MM/YYYY
|
||||||
|
}
|
||||||
|
return datePart; // Fallback si el formato no es el esperado
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
const fetchFiltersDropdownData = useCallback(async () => {
|
const fetchFiltersDropdownData = useCallback(async () => {
|
||||||
setLoadingFiltersDropdown(true);
|
setLoadingFiltersDropdown(true);
|
||||||
@@ -131,16 +143,16 @@ const GestionarControlDevolucionesPage: React.FC = () => {
|
|||||||
|
|
||||||
const handleChangePage = (_event: unknown, newPage: number) => setPage(newPage);
|
const handleChangePage = (_event: unknown, newPage: number) => setPage(newPage);
|
||||||
const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setRowsPerPage(parseInt(event.target.value, 10)); setPage(0);
|
setRowsPerPage(parseInt(event.target.value, 25)); setPage(0);
|
||||||
};
|
};
|
||||||
|
// displayData ahora usará la 'controles' directamente, el formato se aplica en el renderizado
|
||||||
const displayData = controles.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage);
|
const displayData = controles.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage);
|
||||||
const formatDate = (dateString?: string | null) => dateString ? new Date(dateString + 'T00:00:00Z').toLocaleDateString('es-AR') : '-';
|
|
||||||
|
|
||||||
if (!loading && !puedeVer && !loadingFiltersDropdown) return <Box sx={{ p: 2 }}><Alert severity="error">{error || "Acceso denegado."}</Alert></Box>;
|
if (!loading && !puedeVer && !loadingFiltersDropdown) return <Box sx={{ p: 2 }}><Alert severity="error">{error || "Acceso denegado."}</Alert></Box>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 2 }}>
|
<Box sx={{ p: 1 }}>
|
||||||
<Typography variant="h4" gutterBottom>Control de Devoluciones a Empresa</Typography>
|
<Typography variant="h5" gutterBottom>Control de Devoluciones a Empresa</Typography>
|
||||||
<Paper sx={{ p: 2, mb: 2 }}>
|
<Paper sx={{ p: 2, mb: 2 }}>
|
||||||
<Typography variant="h6" gutterBottom>Filtros <FilterListIcon fontSize="small" /></Typography>
|
<Typography variant="h6" gutterBottom>Filtros <FilterListIcon fontSize="small" /></Typography>
|
||||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, alignItems: 'center', mb: 2 }}>
|
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, alignItems: 'center', mb: 2 }}>
|
||||||
@@ -162,13 +174,13 @@ const GestionarControlDevolucionesPage: React.FC = () => {
|
|||||||
{apiErrorMessage && <Alert severity="error" sx={{ my: 2 }}>{apiErrorMessage}</Alert>}
|
{apiErrorMessage && <Alert severity="error" sx={{ my: 2 }}>{apiErrorMessage}</Alert>}
|
||||||
|
|
||||||
{!loading && !error && puedeVer && (
|
{!loading && !error && puedeVer && (
|
||||||
<TableContainer component={Paper}>
|
<TableContainer component={Paper} sx={{ maxHeight: 'calc(100vh - 240px)' }}> {/* Ajusta maxHeight según sea necesario */}
|
||||||
<Table size="small">
|
<Table stickyHeader size="small">
|
||||||
<TableHead><TableRow>
|
<TableHead><TableRow>
|
||||||
<TableCell>Fecha</TableCell><TableCell>Empresa</TableCell>
|
<TableCell>Fecha</TableCell><TableCell>Empresa</TableCell>
|
||||||
<TableCell align="right">Entrada (Total Dev.)</TableCell>
|
<TableCell align="right">Entrada (Por Remito)</TableCell>
|
||||||
<TableCell align="right">Sobrantes</TableCell>
|
<TableCell align="right">Sobrantes</TableCell>
|
||||||
<TableCell align="right">Sin Cargo</TableCell>
|
<TableCell align="right">Ejemplares Sin Cargo</TableCell>
|
||||||
<TableCell>Detalle</TableCell>
|
<TableCell>Detalle</TableCell>
|
||||||
{(puedeModificar || puedeEliminar) && <TableCell align="right">Acciones</TableCell>}
|
{(puedeModificar || puedeEliminar) && <TableCell align="right">Acciones</TableCell>}
|
||||||
</TableRow></TableHead>
|
</TableRow></TableHead>
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import {
|
|||||||
CircularProgress, Alert
|
CircularProgress, Alert
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
|
import EditIcon from '@mui/icons-material/Edit';
|
||||||
|
import TrashIcon from '@mui/icons-material/Delete';
|
||||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||||
import distribuidorService from '../../services/Distribucion/distribuidorService';
|
import distribuidorService from '../../services/Distribucion/distribuidorService';
|
||||||
import type { DistribuidorDto } from '../../models/dtos/Distribucion/DistribuidorDto';
|
import type { DistribuidorDto } from '../../models/dtos/Distribucion/DistribuidorDto';
|
||||||
@@ -110,8 +112,8 @@ const GestionarDistribuidoresPage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 2 }}>
|
<Box sx={{ p: 1 }}>
|
||||||
<Typography variant="h4" gutterBottom>Gestionar Distribuidores</Typography>
|
<Typography variant="h5" gutterBottom>Gestionar Distribuidores</Typography>
|
||||||
<Paper sx={{ p: 2, mb: 2 }}>
|
<Paper sx={{ p: 2, mb: 2 }}>
|
||||||
<Box sx={{ display: 'flex', gap: 2, mb: 2, flexWrap: 'wrap' }}>
|
<Box sx={{ display: 'flex', gap: 2, mb: 2, flexWrap: 'wrap' }}>
|
||||||
<TextField
|
<TextField
|
||||||
@@ -133,9 +135,7 @@ const GestionarDistribuidoresPage: React.FC = () => {
|
|||||||
{/* <Button variant="contained" onClick={cargarDistribuidores} size="small">Buscar</Button> */}
|
{/* <Button variant="contained" onClick={cargarDistribuidores} size="small">Buscar</Button> */}
|
||||||
</Box>
|
</Box>
|
||||||
{puedeCrear && (
|
{puedeCrear && (
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'flex-end', mb: 2 }}>
|
|
||||||
<Button variant="contained" startIcon={<AddIcon />} onClick={() => handleOpenModal()} sx={{ mb: 2 }}>Agregar Distribuidor</Button>
|
<Button variant="contained" startIcon={<AddIcon />} onClick={() => handleOpenModal()} sx={{ mb: 2 }}>Agregar Distribuidor</Button>
|
||||||
</Box>
|
|
||||||
)}
|
)}
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
@@ -179,8 +179,8 @@ const GestionarDistribuidoresPage: React.FC = () => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<Menu anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleMenuClose}>
|
<Menu anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleMenuClose}>
|
||||||
{puedeModificar && (<MenuItem onClick={() => { handleOpenModal(selectedDistribuidorRow!); handleMenuClose(); }}>Modificar</MenuItem>)}
|
{puedeModificar && (<MenuItem onClick={() => { handleOpenModal(selectedDistribuidorRow!); handleMenuClose(); }}><EditIcon fontSize="small" sx={{ mr: 1 }} />Modificar</MenuItem>)}
|
||||||
{puedeEliminar && (<MenuItem onClick={() => handleDelete(selectedDistribuidorRow!.idDistribuidor)}>Eliminar</MenuItem>)}
|
{puedeEliminar && (<MenuItem onClick={() => handleDelete(selectedDistribuidorRow!.idDistribuidor)}><TrashIcon fontSize="small" sx={{ mr: 1 }} />Eliminar</MenuItem>)}
|
||||||
{(!puedeModificar && !puedeEliminar) && <MenuItem disabled>Sin acciones</MenuItem>}
|
{(!puedeModificar && !puedeEliminar) && <MenuItem disabled>Sin acciones</MenuItem>}
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,14 @@ import React, { useState, useEffect, useCallback } from 'react';
|
|||||||
import {
|
import {
|
||||||
Box, Typography, TextField, Button, Paper, IconButton, Menu, MenuItem,
|
Box, Typography, TextField, Button, Paper, IconButton, Menu, MenuItem,
|
||||||
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TablePagination,
|
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TablePagination,
|
||||||
CircularProgress, Alert
|
CircularProgress, Alert,
|
||||||
|
ListItemIcon,
|
||||||
|
ListItemText
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
import MoreVertIcon from '@mui/icons-material/MoreVert'; // Icono para más opciones
|
||||||
|
import EditIcon from '@mui/icons-material/Edit'; // Icono para modificar
|
||||||
|
import DeleteIcon from '@mui/icons-material/Delete'; // Icono para eliminar
|
||||||
import empresaService from '../../services/Distribucion/empresaService'; // Importar el servicio de Empresas
|
import empresaService from '../../services/Distribucion/empresaService'; // Importar el servicio de Empresas
|
||||||
import type { EmpresaDto } from '../../models/dtos/Distribucion/EmpresaDto';
|
import type { EmpresaDto } from '../../models/dtos/Distribucion/EmpresaDto';
|
||||||
import type { CreateEmpresaDto } from '../../models/dtos/Distribucion/CreateEmpresaDto';
|
import type { CreateEmpresaDto } from '../../models/dtos/Distribucion/CreateEmpresaDto';
|
||||||
@@ -150,8 +154,8 @@ const GestionarEmpresasPage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 2 }}>
|
<Box sx={{ p: 1 }}>
|
||||||
<Typography variant="h4" gutterBottom>
|
<Typography variant="h5" gutterBottom>
|
||||||
Gestionar Empresas
|
Gestionar Empresas
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
@@ -167,7 +171,6 @@ const GestionarEmpresasPage: React.FC = () => {
|
|||||||
</Box>
|
</Box>
|
||||||
{/* Mostrar botón de agregar solo si tiene permiso */}
|
{/* Mostrar botón de agregar solo si tiene permiso */}
|
||||||
{puedeCrear && (
|
{puedeCrear && (
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'flex-end', mb: 2 }}>
|
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
startIcon={<AddIcon />}
|
startIcon={<AddIcon />}
|
||||||
@@ -175,7 +178,6 @@ const GestionarEmpresasPage: React.FC = () => {
|
|||||||
>
|
>
|
||||||
Agregar Nueva Empresa
|
Agregar Nueva Empresa
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
|
||||||
)}
|
)}
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
@@ -246,13 +248,15 @@ const GestionarEmpresasPage: React.FC = () => {
|
|||||||
{/* Mostrar opción Modificar solo si tiene permiso */}
|
{/* Mostrar opción Modificar solo si tiene permiso */}
|
||||||
{puedeModificar && (
|
{puedeModificar && (
|
||||||
<MenuItem onClick={() => { handleOpenModal(selectedEmpresaRow!); handleMenuClose(); }}>
|
<MenuItem onClick={() => { handleOpenModal(selectedEmpresaRow!); handleMenuClose(); }}>
|
||||||
Modificar
|
<ListItemIcon><EditIcon fontSize="small" /></ListItemIcon>
|
||||||
|
<ListItemText>Modificar</ListItemText>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
{/* Mostrar opción Eliminar solo si tiene permiso */}
|
{/* Mostrar opción Eliminar solo si tiene permiso */}
|
||||||
{puedeEliminar && (
|
{puedeEliminar && (
|
||||||
<MenuItem onClick={() => handleDelete(selectedEmpresaRow!.idEmpresa)}>
|
<MenuItem onClick={() => handleDelete(selectedEmpresaRow!.idEmpresa)}>
|
||||||
Eliminar
|
<ListItemIcon><DeleteIcon fontSize="small" /></ListItemIcon>
|
||||||
|
<ListItemText>Eliminar</ListItemText>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
{/* Mensaje si no hay acciones disponibles (por si acaso) */}
|
{/* Mensaje si no hay acciones disponibles (por si acaso) */}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// src/pages/Distribucion/GestionarEntradasSalidasCanillaPage.tsx
|
|
||||||
import React, { useState, useEffect, useCallback } from 'react';
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
import {
|
import {
|
||||||
Box, Typography, TextField, Button, Paper, IconButton, Menu, MenuItem, Chip,
|
Box, Typography, TextField, Button, Paper, IconButton, Menu, MenuItem, Chip,
|
||||||
@@ -7,27 +6,27 @@ import {
|
|||||||
Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle
|
Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
|
import PrintIcon from '@mui/icons-material/Print';
|
||||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||||
import EditIcon from '@mui/icons-material/Edit';
|
import EditIcon from '@mui/icons-material/Edit';
|
||||||
import DeleteIcon from '@mui/icons-material/Delete';
|
import DeleteIcon from '@mui/icons-material/Delete';
|
||||||
import FilterListIcon from '@mui/icons-material/FilterList';
|
import FilterListIcon from '@mui/icons-material/FilterList';
|
||||||
import PlaylistAddCheckIcon from '@mui/icons-material/PlaylistAddCheck'; // Para Liquidar
|
import PlaylistAddCheckIcon from '@mui/icons-material/PlaylistAddCheck';
|
||||||
|
|
||||||
import entradaSalidaCanillaService from '../../services/Distribucion/entradaSalidaCanillaService';
|
import entradaSalidaCanillaService from '../../services/Distribucion/entradaSalidaCanillaService';
|
||||||
import publicacionService from '../../services/Distribucion/publicacionService';
|
import publicacionService from '../../services/Distribucion/publicacionService';
|
||||||
import canillaService from '../../services/Distribucion/canillaService';
|
import canillaService from '../../services/Distribucion/canillaService';
|
||||||
|
|
||||||
import type { EntradaSalidaCanillaDto } from '../../models/dtos/Distribucion/EntradaSalidaCanillaDto';
|
import type { EntradaSalidaCanillaDto } from '../../models/dtos/Distribucion/EntradaSalidaCanillaDto';
|
||||||
import type { CreateEntradaSalidaCanillaDto } from '../../models/dtos/Distribucion/CreateEntradaSalidaCanillaDto';
|
|
||||||
import type { UpdateEntradaSalidaCanillaDto } from '../../models/dtos/Distribucion/UpdateEntradaSalidaCanillaDto';
|
import type { UpdateEntradaSalidaCanillaDto } from '../../models/dtos/Distribucion/UpdateEntradaSalidaCanillaDto';
|
||||||
import type { PublicacionDto } from '../../models/dtos/Distribucion/PublicacionDto';
|
import type { PublicacionDto } from '../../models/dtos/Distribucion/PublicacionDto';
|
||||||
import type { CanillaDto } from '../../models/dtos/Distribucion/CanillaDto';
|
import type { CanillaDto } from '../../models/dtos/Distribucion/CanillaDto';
|
||||||
import type { LiquidarMovimientosCanillaRequestDto } from '../../models/dtos/Distribucion/LiquidarMovimientosCanillaDto';
|
import type { LiquidarMovimientosCanillaRequestDto } from '../../models/dtos/Distribucion/LiquidarMovimientosCanillaDto';
|
||||||
|
|
||||||
|
|
||||||
import EntradaSalidaCanillaFormModal from '../../components/Modals/Distribucion/EntradaSalidaCanillaFormModal';
|
import EntradaSalidaCanillaFormModal from '../../components/Modals/Distribucion/EntradaSalidaCanillaFormModal';
|
||||||
import { usePermissions } from '../../hooks/usePermissions';
|
import { usePermissions } from '../../hooks/usePermissions';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import reportesService from '../../services/Reportes/reportesService';
|
||||||
|
|
||||||
const GestionarEntradasSalidasCanillaPage: React.FC = () => {
|
const GestionarEntradasSalidasCanillaPage: React.FC = () => {
|
||||||
const [movimientos, setMovimientos] = useState<EntradaSalidaCanillaDto[]>([]);
|
const [movimientos, setMovimientos] = useState<EntradaSalidaCanillaDto[]>([]);
|
||||||
@@ -35,13 +34,12 @@ const GestionarEntradasSalidasCanillaPage: React.FC = () => {
|
|||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [apiErrorMessage, setApiErrorMessage] = useState<string | null>(null);
|
const [apiErrorMessage, setApiErrorMessage] = useState<string | null>(null);
|
||||||
|
|
||||||
// Filtros
|
const [filtroFechaDesde, setFiltroFechaDesde] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||||
const [filtroFechaDesde, setFiltroFechaDesde] = useState<string>(new Date().toISOString().split('T')[0]); //useState('');
|
const [filtroFechaHasta, setFiltroFechaHasta] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||||
const [filtroFechaHasta, setFiltroFechaHasta] = useState<string>(new Date().toISOString().split('T')[0]); //useState('');
|
|
||||||
const [filtroIdPublicacion, setFiltroIdPublicacion] = useState<number | string>('');
|
const [filtroIdPublicacion, setFiltroIdPublicacion] = useState<number | string>('');
|
||||||
const [filtroIdCanilla, setFiltroIdCanilla] = useState<number | string>('');
|
const [filtroIdCanilla, setFiltroIdCanilla] = useState<number | string>('');
|
||||||
const [filtroEstadoLiquidacion, setFiltroEstadoLiquidacion] = useState<'todos' | 'liquidados' | 'noLiquidados'>('noLiquidados');
|
const [filtroEstadoLiquidacion, setFiltroEstadoLiquidacion] = useState<'todos' | 'liquidados' | 'noLiquidados'>('noLiquidados');
|
||||||
|
const [loadingTicketPdf, setLoadingTicketPdf] = useState(false);
|
||||||
|
|
||||||
const [publicaciones, setPublicaciones] = useState<PublicacionDto[]>([]);
|
const [publicaciones, setPublicaciones] = useState<PublicacionDto[]>([]);
|
||||||
const [canillitas, setCanillitas] = useState<CanillaDto[]>([]);
|
const [canillitas, setCanillitas] = useState<CanillaDto[]>([]);
|
||||||
@@ -58,9 +56,7 @@ const GestionarEntradasSalidasCanillaPage: React.FC = () => {
|
|||||||
const [fechaLiquidacionDialog, setFechaLiquidacionDialog] = useState<string>(new Date().toISOString().split('T')[0]);
|
const [fechaLiquidacionDialog, setFechaLiquidacionDialog] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||||
const [openLiquidarDialog, setOpenLiquidarDialog] = useState(false);
|
const [openLiquidarDialog, setOpenLiquidarDialog] = useState(false);
|
||||||
|
|
||||||
|
|
||||||
const { tienePermiso, isSuperAdmin } = usePermissions();
|
const { tienePermiso, isSuperAdmin } = usePermissions();
|
||||||
// MC001 (Ver), MC002 (Crear), MC003 (Modificar), MC004 (Eliminar), MC005 (Liquidar)
|
|
||||||
const puedeVer = isSuperAdmin || tienePermiso("MC001");
|
const puedeVer = isSuperAdmin || tienePermiso("MC001");
|
||||||
const puedeCrear = isSuperAdmin || tienePermiso("MC002");
|
const puedeCrear = isSuperAdmin || tienePermiso("MC002");
|
||||||
const puedeModificar = isSuperAdmin || tienePermiso("MC003");
|
const puedeModificar = isSuperAdmin || tienePermiso("MC003");
|
||||||
@@ -68,6 +64,17 @@ const GestionarEntradasSalidasCanillaPage: React.FC = () => {
|
|||||||
const puedeLiquidar = isSuperAdmin || tienePermiso("MC005");
|
const puedeLiquidar = isSuperAdmin || tienePermiso("MC005");
|
||||||
const puedeEliminarLiquidados = isSuperAdmin || tienePermiso("MC006");
|
const puedeEliminarLiquidados = isSuperAdmin || tienePermiso("MC006");
|
||||||
|
|
||||||
|
// Función para formatear fechas YYYY-MM-DD a DD/MM/YYYY
|
||||||
|
const formatDate = (dateString?: string | null): string => {
|
||||||
|
if (!dateString) return '-';
|
||||||
|
const datePart = dateString.split('T')[0];
|
||||||
|
const parts = datePart.split('-');
|
||||||
|
if (parts.length === 3) {
|
||||||
|
return `${parts[2]}/${parts[1]}/${parts[0]}`;
|
||||||
|
}
|
||||||
|
return datePart;
|
||||||
|
};
|
||||||
|
|
||||||
const fetchFiltersDropdownData = useCallback(async () => {
|
const fetchFiltersDropdownData = useCallback(async () => {
|
||||||
setLoadingFiltersDropdown(true);
|
setLoadingFiltersDropdown(true);
|
||||||
try {
|
try {
|
||||||
@@ -120,22 +127,6 @@ const GestionarEntradasSalidasCanillaPage: React.FC = () => {
|
|||||||
const handleOpenModal = (item?: EntradaSalidaCanillaDto) => {
|
const handleOpenModal = (item?: EntradaSalidaCanillaDto) => {
|
||||||
setEditingMovimiento(item || null); setApiErrorMessage(null); setModalOpen(true);
|
setEditingMovimiento(item || null); setApiErrorMessage(null); setModalOpen(true);
|
||||||
};
|
};
|
||||||
const handleCloseModal = () => { setModalOpen(false); setEditingMovimiento(null); };
|
|
||||||
|
|
||||||
const handleSubmitModal = async (data: CreateEntradaSalidaCanillaDto | UpdateEntradaSalidaCanillaDto, idParte?: number) => {
|
|
||||||
setApiErrorMessage(null);
|
|
||||||
try {
|
|
||||||
if (idParte && editingMovimiento) {
|
|
||||||
await entradaSalidaCanillaService.updateEntradaSalidaCanilla(idParte, data as UpdateEntradaSalidaCanillaDto);
|
|
||||||
} else {
|
|
||||||
await entradaSalidaCanillaService.createEntradaSalidaCanilla(data as CreateEntradaSalidaCanillaDto);
|
|
||||||
}
|
|
||||||
cargarMovimientos();
|
|
||||||
} catch (err: any) {
|
|
||||||
const message = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : 'Error al guardar.';
|
|
||||||
setApiErrorMessage(message); throw err;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDelete = async (idParte: number) => {
|
const handleDelete = async (idParte: number) => {
|
||||||
if (window.confirm(`¿Seguro de eliminar este movimiento (ID: ${idParte})?`)) {
|
if (window.confirm(`¿Seguro de eliminar este movimiento (ID: ${idParte})?`)) {
|
||||||
@@ -147,7 +138,10 @@ const GestionarEntradasSalidasCanillaPage: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleMenuOpen = (event: React.MouseEvent<HTMLElement>, item: EntradaSalidaCanillaDto) => {
|
const handleMenuOpen = (event: React.MouseEvent<HTMLElement>, item: EntradaSalidaCanillaDto) => {
|
||||||
setAnchorEl(event.currentTarget); setSelectedRow(item);
|
// Almacenar el idParte en el propio elemento del menú para referencia
|
||||||
|
event.currentTarget.setAttribute('data-rowid', item.idParte.toString());
|
||||||
|
setAnchorEl(event.currentTarget);
|
||||||
|
setSelectedRow(item);
|
||||||
};
|
};
|
||||||
const handleMenuClose = () => { setAnchorEl(null); setSelectedRow(null); };
|
const handleMenuClose = () => { setAnchorEl(null); setSelectedRow(null); };
|
||||||
|
|
||||||
@@ -177,15 +171,63 @@ const GestionarEntradasSalidasCanillaPage: React.FC = () => {
|
|||||||
};
|
};
|
||||||
const handleCloseLiquidarDialog = () => setOpenLiquidarDialog(false);
|
const handleCloseLiquidarDialog = () => setOpenLiquidarDialog(false);
|
||||||
const handleConfirmLiquidar = async () => {
|
const handleConfirmLiquidar = async () => {
|
||||||
setApiErrorMessage(null); setLoading(true);
|
if (selectedIdsParaLiquidar.size === 0) {
|
||||||
|
setApiErrorMessage("No hay movimientos seleccionados para liquidar.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!fechaLiquidacionDialog) {
|
||||||
|
setApiErrorMessage("Debe seleccionar una fecha de liquidación.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- VALIDACIÓN DE FECHA ---
|
||||||
|
const fechaLiquidacionDate = new Date(fechaLiquidacionDialog + 'T00:00:00Z'); // Usar Z para consistencia con formatDate si es necesario, o T00:00:00 para local
|
||||||
|
|
||||||
|
let fechaMovimientoMasReciente: Date | null = null;
|
||||||
|
|
||||||
|
selectedIdsParaLiquidar.forEach(idParte => {
|
||||||
|
const movimiento = movimientos.find(m => m.idParte === idParte);
|
||||||
|
if (movimiento && movimiento.fecha) { // Asegurarse que movimiento.fecha existe
|
||||||
|
const movFecha = new Date(movimiento.fecha.split('T')[0] + 'T00:00:00Z'); // Consistencia con Z
|
||||||
|
if (fechaMovimientoMasReciente === null || movFecha.getTime() > (fechaMovimientoMasReciente as Date).getTime()) { // Comparar usando getTime()
|
||||||
|
fechaMovimientoMasReciente = movFecha;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (fechaMovimientoMasReciente !== null && fechaLiquidacionDate.getTime() < (fechaMovimientoMasReciente as Date).getTime()) { // Comparar usando getTime()
|
||||||
|
setApiErrorMessage(`La fecha de liquidación (${fechaLiquidacionDate.toLocaleDateString('es-AR', {timeZone: 'UTC'})}) no puede ser inferior a la fecha del movimiento más reciente a liquidar (${(fechaMovimientoMasReciente as Date).toLocaleDateString('es-AR', {timeZone: 'UTC'})}).`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setApiErrorMessage(null);
|
||||||
|
setLoading(true); // Usar el loading general para la operación de liquidar
|
||||||
|
|
||||||
const liquidarDto: LiquidarMovimientosCanillaRequestDto = {
|
const liquidarDto: LiquidarMovimientosCanillaRequestDto = {
|
||||||
idsPartesALiquidar: Array.from(selectedIdsParaLiquidar),
|
idsPartesALiquidar: Array.from(selectedIdsParaLiquidar),
|
||||||
fechaLiquidacion: fechaLiquidacionDialog
|
fechaLiquidacion: fechaLiquidacionDialog // El backend espera YYYY-MM-DD
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await entradaSalidaCanillaService.liquidarMovimientos(liquidarDto);
|
await entradaSalidaCanillaService.liquidarMovimientos(liquidarDto);
|
||||||
cargarMovimientos(); // Recargar para ver los cambios
|
|
||||||
setOpenLiquidarDialog(false);
|
setOpenLiquidarDialog(false);
|
||||||
|
|
||||||
|
const primerIdParteLiquidado = Array.from(selectedIdsParaLiquidar)[0];
|
||||||
|
const movimientoParaTicket = movimientos.find(m => m.idParte === primerIdParteLiquidado);
|
||||||
|
|
||||||
|
await cargarMovimientos();
|
||||||
|
|
||||||
|
if (movimientoParaTicket) {
|
||||||
|
console.log("Liquidación exitosa, intentando generar ticket para canillita:", movimientoParaTicket.idCanilla);
|
||||||
|
await handleImprimirTicketLiquidacion(
|
||||||
|
movimientoParaTicket.idCanilla,
|
||||||
|
fechaLiquidacionDialog,
|
||||||
|
movimientoParaTicket.canillaEsAccionista
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.warn("No se pudo encontrar información del movimiento para generar el ticket post-liquidación.");
|
||||||
|
}
|
||||||
|
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
const msg = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : 'Error al liquidar.';
|
const msg = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : 'Error al liquidar.';
|
||||||
setApiErrorMessage(msg);
|
setApiErrorMessage(msg);
|
||||||
@@ -194,23 +236,85 @@ const GestionarEntradasSalidasCanillaPage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Esta función se pasa al modal para que la invoque al hacer submit en MODO EDICIÓN
|
||||||
|
const handleModalEditSubmit = async (data: UpdateEntradaSalidaCanillaDto, idParte: number) => {
|
||||||
|
setApiErrorMessage(null);
|
||||||
|
try {
|
||||||
|
await entradaSalidaCanillaService.updateEntradaSalidaCanilla(idParte, data);
|
||||||
|
} catch (err: any) {
|
||||||
|
const message = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : 'Error al guardar los cambios.';
|
||||||
|
setApiErrorMessage(message);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseModal = () => {
|
||||||
|
setModalOpen(false);
|
||||||
|
setEditingMovimiento(null);
|
||||||
|
// Recargar siempre que se cierre el modal y no haya un error pendiente a nivel de página
|
||||||
|
// Opcionalmente, podrías tener una bandera ' cambiosGuardados' que el modal active
|
||||||
|
// para ser más selectivo con la recarga.
|
||||||
|
if (!apiErrorMessage) {
|
||||||
|
cargarMovimientos();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleImprimirTicketLiquidacion = useCallback(async (
|
||||||
|
// Parámetros necesarios para el ticket
|
||||||
|
idCanilla: number,
|
||||||
|
fecha: string, // Fecha para la que se genera el ticket (probablemente fechaLiquidacionDialog)
|
||||||
|
esAccionista: boolean
|
||||||
|
) => {
|
||||||
|
setLoadingTicketPdf(true);
|
||||||
|
setApiErrorMessage(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
fecha: fecha.split('T')[0], // Asegurar formato YYYY-MM-DD
|
||||||
|
idCanilla: idCanilla,
|
||||||
|
esAccionista: esAccionista,
|
||||||
|
};
|
||||||
|
|
||||||
|
const blob = await reportesService.getTicketLiquidacionCanillaPdf(params);
|
||||||
|
|
||||||
|
if (blob.type === "application/json") {
|
||||||
|
const text = await blob.text();
|
||||||
|
const msg = JSON.parse(text).message ?? "Error inesperado al generar el ticket PDF.";
|
||||||
|
setApiErrorMessage(msg);
|
||||||
|
} else {
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const w = window.open(url, '_blank');
|
||||||
|
if (!w) alert("Permita popups para ver el PDF del ticket.");
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("Error al generar ticket de liquidación:", error);
|
||||||
|
const message = axios.isAxiosError(error) && error.response?.data?.message
|
||||||
|
? error.response.data.message
|
||||||
|
: 'Ocurrió un error al generar el ticket.';
|
||||||
|
setApiErrorMessage(message);
|
||||||
|
} finally {
|
||||||
|
setLoadingTicketPdf(false);
|
||||||
|
// No cerramos el menú aquí si se llama desde handleConfirmLiquidar
|
||||||
|
}
|
||||||
|
}, []); // Dependencias vacías si no usa nada del scope exterior que cambie, o añadir si es necesario
|
||||||
|
|
||||||
|
|
||||||
const handleChangePage = (_event: unknown, newPage: number) => setPage(newPage);
|
const handleChangePage = (_event: unknown, newPage: number) => setPage(newPage);
|
||||||
const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setRowsPerPage(parseInt(event.target.value, 10)); setPage(0);
|
setRowsPerPage(parseInt(event.target.value, 10)); setPage(0);
|
||||||
};
|
};
|
||||||
const displayData = movimientos.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage);
|
const displayData = movimientos.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage);
|
||||||
const formatDate = (dateString?: string | null) => dateString ? new Date(dateString + 'T00:00:00Z').toLocaleDateString('es-AR') : '-';
|
|
||||||
|
|
||||||
if (!loading && !puedeVer && !loadingFiltersDropdown) return <Box sx={{ p: 2 }}><Alert severity="error">{error || "Acceso denegado."}</Alert></Box>;
|
if (!loading && !puedeVer && !loadingFiltersDropdown) return <Box sx={{ p: 2 }}><Alert severity="error">{error || "Acceso denegado."}</Alert></Box>;
|
||||||
|
|
||||||
const numSelectedToLiquidate = selectedIdsParaLiquidar.size;
|
const numSelectedToLiquidate = selectedIdsParaLiquidar.size;
|
||||||
|
// Corregido: numNotLiquidatedOnPage debe calcularse sobre 'movimientos' filtrados, no solo 'displayData'
|
||||||
|
// O, si la selección es solo por página, displayData está bien. Asumamos selección por página por ahora.
|
||||||
const numNotLiquidatedOnPage = displayData.filter(m => !m.liquidado).length;
|
const numNotLiquidatedOnPage = displayData.filter(m => !m.liquidado).length;
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 2 }}>
|
<Box sx={{ p: 1 }}>
|
||||||
<Typography variant="h4" gutterBottom>Entradas/Salidas Canillitas</Typography>
|
<Typography variant="h5" gutterBottom>Entradas/Salidas Canillitas</Typography>
|
||||||
<Paper sx={{ p: 2, mb: 2 }}>
|
<Paper sx={{ p: 2, mb: 2 }}>
|
||||||
<Typography variant="h6" gutterBottom>Filtros <FilterListIcon fontSize="small" /></Typography>
|
<Typography variant="h6" gutterBottom>Filtros <FilterListIcon fontSize="small" /></Typography>
|
||||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, alignItems: 'center', mb: 2 }}>
|
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, alignItems: 'center', mb: 2 }}>
|
||||||
@@ -250,36 +354,62 @@ const GestionarEntradasSalidasCanillaPage: React.FC = () => {
|
|||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
{loading && <Box sx={{ display: 'flex', justifyContent: 'center', my: 2 }}><CircularProgress /></Box>}
|
{loading && <Box sx={{ display: 'flex', justifyContent: 'center', my: 2 }}><CircularProgress /></Box>}
|
||||||
{error && !loading && <Alert severity="error" sx={{ my: 2 }}>{error}</Alert>}
|
{error && !loading && !apiErrorMessage && <Alert severity="error" sx={{ my: 2 }}>{error}</Alert>}
|
||||||
{apiErrorMessage && <Alert severity="error" sx={{ my: 2 }}>{apiErrorMessage}</Alert>}
|
{apiErrorMessage && <Alert severity="error" sx={{ my: 2 }}>{apiErrorMessage}</Alert>}
|
||||||
|
{loadingTicketPdf &&
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', my: 2 }}>
|
||||||
|
<CircularProgress size={20} sx={{ mr: 1 }} />
|
||||||
|
<Typography variant="body2">Cargando ticket...</Typography>
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
{!loading && !error && puedeVer && (
|
{!loading && !error && puedeVer && (
|
||||||
<TableContainer component={Paper}>
|
<TableContainer component={Paper}>
|
||||||
<Table size="small">
|
<Table size="small">
|
||||||
<TableHead><TableRow>
|
<TableHead>
|
||||||
{puedeLiquidar && filtroEstadoLiquidacion !== 'liquidados' &&
|
<TableRow>
|
||||||
|
{puedeLiquidar && filtroEstadoLiquidacion !== 'liquidados' && (
|
||||||
<TableCell padding="checkbox">
|
<TableCell padding="checkbox">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
indeterminate={numSelectedToLiquidate > 0 && numSelectedToLiquidate < numNotLiquidatedOnPage}
|
indeterminate={numSelectedToLiquidate > 0 && numSelectedToLiquidate < numNotLiquidatedOnPage && numNotLiquidatedOnPage > 0}
|
||||||
checked={numNotLiquidatedOnPage > 0 && numSelectedToLiquidate === numNotLiquidatedOnPage}
|
checked={numNotLiquidatedOnPage > 0 && numSelectedToLiquidate === numNotLiquidatedOnPage}
|
||||||
onChange={handleSelectAllForLiquidar}
|
onChange={handleSelectAllForLiquidar}
|
||||||
disabled={numNotLiquidatedOnPage === 0}
|
disabled={numNotLiquidatedOnPage === 0}
|
||||||
/>
|
/>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
}
|
)}
|
||||||
<TableCell>Fecha</TableCell><TableCell>Publicación</TableCell><TableCell>Canillita</TableCell>
|
<TableCell>Fecha</TableCell>
|
||||||
<TableCell align="right">Salida</TableCell><TableCell align="right">Entrada</TableCell>
|
<TableCell>Publicación</TableCell>
|
||||||
<TableCell align="right">Vendidos</TableCell><TableCell align="right">A Rendir</TableCell>
|
<TableCell>Canillita</TableCell>
|
||||||
<TableCell>Liquidado</TableCell><TableCell>F. Liq.</TableCell><TableCell>Obs.</TableCell>
|
<TableCell align="right">Salida</TableCell>
|
||||||
{(puedeModificar || puedeEliminar) && <TableCell align="right">Acciones</TableCell>}
|
<TableCell align="right">Entrada</TableCell>
|
||||||
</TableRow></TableHead>
|
<TableCell align="right">Vendidos</TableCell>
|
||||||
|
<TableCell align="right">A Rendir</TableCell>
|
||||||
|
<TableCell>Liquidado</TableCell>
|
||||||
|
<TableCell>F. Liq.</TableCell>
|
||||||
|
<TableCell>Obs.</TableCell>
|
||||||
|
{(puedeModificar || puedeEliminar || puedeLiquidar) && <TableCell align="right">Acciones</TableCell>}
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{displayData.length === 0 ? (
|
{displayData.length === 0 ? (
|
||||||
<TableRow><TableCell colSpan={puedeLiquidar && filtroEstadoLiquidacion !== 'liquidados' ? 12 : 11} align="center">No se encontraron movimientos.</TableCell></TableRow>
|
<TableRow>
|
||||||
|
<TableCell
|
||||||
|
colSpan={
|
||||||
|
(puedeLiquidar && filtroEstadoLiquidacion !== 'liquidados' ? 1 : 0) +
|
||||||
|
9 +
|
||||||
|
((puedeModificar || puedeEliminar || puedeLiquidar) ? 1 : 0)
|
||||||
|
}
|
||||||
|
align="center"
|
||||||
|
>
|
||||||
|
No se encontraron movimientos.
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
) : (
|
) : (
|
||||||
displayData.map((m) => (
|
displayData.map((m) => (
|
||||||
<TableRow key={m.idParte} hover selected={selectedIdsParaLiquidar.has(m.idParte)}>
|
<TableRow key={m.idParte} hover selected={selectedIdsParaLiquidar.has(m.idParte)}>
|
||||||
{puedeLiquidar && filtroEstadoLiquidacion !== 'liquidados' &&
|
{puedeLiquidar && filtroEstadoLiquidacion !== 'liquidados' && (
|
||||||
<TableCell padding="checkbox">
|
<TableCell padding="checkbox">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={selectedIdsParaLiquidar.has(m.idParte)}
|
checked={selectedIdsParaLiquidar.has(m.idParte)}
|
||||||
@@ -287,26 +417,31 @@ const GestionarEntradasSalidasCanillaPage: React.FC = () => {
|
|||||||
disabled={m.liquidado}
|
disabled={m.liquidado}
|
||||||
/>
|
/>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
}
|
)}
|
||||||
<TableCell>{formatDate(m.fecha)}</TableCell>
|
<TableCell>{formatDate(m.fecha)}</TableCell>
|
||||||
<TableCell>{m.nombrePublicacion}</TableCell>
|
<TableCell>{m.nombrePublicacion}</TableCell>
|
||||||
<TableCell>{m.nomApeCanilla}</TableCell>
|
<TableCell>{m.nomApeCanilla}</TableCell>
|
||||||
<TableCell align="right">{m.cantSalida}</TableCell>
|
<TableCell align="right">{m.cantSalida}</TableCell>
|
||||||
<TableCell align="right">{m.cantEntrada}</TableCell>
|
<TableCell align="right">{m.cantEntrada}</TableCell>
|
||||||
<TableCell align="right" sx={{ fontWeight: 'bold' }}>{m.vendidos}</TableCell>
|
<TableCell align="right" sx={{ fontWeight: 'bold' }}>{m.vendidos}</TableCell>
|
||||||
<TableCell align="right" sx={{ fontWeight: 'bold' }}>${m.montoARendir.toFixed(2)}</TableCell>
|
<TableCell align="right" sx={{ fontWeight: 'bold' }}>
|
||||||
|
{m.montoARendir.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' })}
|
||||||
|
</TableCell>
|
||||||
<TableCell align="center">{m.liquidado ? <Chip label="Sí" color="success" size="small" /> : <Chip label="No" size="small" />}</TableCell>
|
<TableCell align="center">{m.liquidado ? <Chip label="Sí" color="success" size="small" /> : <Chip label="No" size="small" />}</TableCell>
|
||||||
<TableCell>{m.fechaLiquidado ? formatDate(m.fechaLiquidado) : '-'}</TableCell>
|
<TableCell>{m.fechaLiquidado ? formatDate(m.fechaLiquidado) : '-'}</TableCell>
|
||||||
<TableCell><Tooltip title={m.observacion || ''}><Box sx={{ maxWidth: 100, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{m.observacion || '-'}</Box></Tooltip></TableCell>
|
<TableCell>
|
||||||
{(puedeModificar || puedeEliminar) && (
|
<Tooltip title={m.observacion || ''}>
|
||||||
|
<Box sx={{ maxWidth: 100, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
||||||
|
{m.observacion || '-'}
|
||||||
|
</Box>
|
||||||
|
</Tooltip>
|
||||||
|
</TableCell>
|
||||||
|
{(puedeModificar || puedeEliminar || puedeLiquidar) && (
|
||||||
<TableCell align="right">
|
<TableCell align="right">
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={(e) => handleMenuOpen(e, m)}
|
onClick={(e) => handleMenuOpen(e, m)}
|
||||||
disabled={
|
data-rowid={m.idParte.toString()} // Guardar el id de la fila aquí
|
||||||
// Deshabilitar si no tiene ningún permiso de eliminación O
|
disabled={m.liquidado && !puedeEliminarLiquidados && !puedeLiquidar} // Lógica simplificada, refinar si es necesario
|
||||||
// si está liquidado y no tiene permiso para eliminar liquidados
|
|
||||||
!((!m.liquidado && puedeEliminar) || (m.liquidado && puedeEliminarLiquidados))
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<MoreVertIcon />
|
<MoreVertIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
@@ -327,18 +462,45 @@ const GestionarEntradasSalidasCanillaPage: React.FC = () => {
|
|||||||
<Menu anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleMenuClose}>
|
<Menu anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleMenuClose}>
|
||||||
{puedeModificar && selectedRow && !selectedRow.liquidado && (
|
{puedeModificar && selectedRow && !selectedRow.liquidado && (
|
||||||
<MenuItem onClick={() => { handleOpenModal(selectedRow); handleMenuClose(); }}><EditIcon fontSize="small" sx={{ mr: 1 }} /> Modificar</MenuItem>)}
|
<MenuItem onClick={() => { handleOpenModal(selectedRow); handleMenuClose(); }}><EditIcon fontSize="small" sx={{ mr: 1 }} /> Modificar</MenuItem>)}
|
||||||
{selectedRow && (
|
|
||||||
(!selectedRow.liquidado && puedeEliminar) || (selectedRow.liquidado && puedeEliminarLiquidados)
|
{/* Opción de Imprimir Ticket Liq. */}
|
||||||
|
{selectedRow && selectedRow.liquidado && ( // Solo mostrar si ya está liquidado (para reimprimir)
|
||||||
|
<MenuItem
|
||||||
|
onClick={() => {
|
||||||
|
if (selectedRow) { // selectedRow no será null aquí debido a la condición anterior
|
||||||
|
handleImprimirTicketLiquidacion(
|
||||||
|
selectedRow.idCanilla,
|
||||||
|
selectedRow.fechaLiquidado || selectedRow.fecha, // Usar fechaLiquidado si existe, sino la fecha del movimiento
|
||||||
|
selectedRow.canillaEsAccionista
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// handleMenuClose() es llamado por handleImprimirTicketLiquidacion
|
||||||
|
}}
|
||||||
|
disabled={loadingTicketPdf}
|
||||||
|
>
|
||||||
|
<PrintIcon fontSize="small" sx={{ mr: 1 }} />
|
||||||
|
{loadingTicketPdf && <CircularProgress size={16} sx={{ mr: 1 }} />}
|
||||||
|
Reimprimir Ticket Liq.
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{selectedRow && ( // Opción de Eliminar
|
||||||
|
((!selectedRow.liquidado && puedeEliminar) || (selectedRow.liquidado && puedeEliminarLiquidados))
|
||||||
) && (
|
) && (
|
||||||
<MenuItem onClick={() => handleDelete(selectedRow.idParte)}>
|
<MenuItem onClick={() => {
|
||||||
|
if (selectedRow) handleDelete(selectedRow.idParte);
|
||||||
|
}}>
|
||||||
<DeleteIcon fontSize="small" sx={{ mr: 1 }} /> Eliminar
|
<DeleteIcon fontSize="small" sx={{ mr: 1 }} /> Eliminar
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|
||||||
<EntradaSalidaCanillaFormModal
|
<EntradaSalidaCanillaFormModal
|
||||||
open={modalOpen} onClose={handleCloseModal} onSubmit={handleSubmitModal}
|
open={modalOpen}
|
||||||
initialData={editingMovimiento} errorMessage={apiErrorMessage}
|
onClose={handleCloseModal}
|
||||||
|
onSubmit={handleModalEditSubmit}
|
||||||
|
initialData={editingMovimiento}
|
||||||
|
errorMessage={apiErrorMessage}
|
||||||
clearErrorMessage={() => setApiErrorMessage(null)}
|
clearErrorMessage={() => setApiErrorMessage(null)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// src/pages/Distribucion/GestionarEntradasSalidasDistPage.tsx
|
|
||||||
import React, { useState, useEffect, useCallback } from 'react';
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
import {
|
import {
|
||||||
Box, Typography, TextField, Button, Paper, IconButton, Menu, MenuItem, Chip,
|
Box, Typography, TextField, Button, Paper, IconButton, Menu, MenuItem, Chip,
|
||||||
@@ -31,14 +30,12 @@ const GestionarEntradasSalidasDistPage: React.FC = () => {
|
|||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [apiErrorMessage, setApiErrorMessage] = useState<string | null>(null);
|
const [apiErrorMessage, setApiErrorMessage] = useState<string | null>(null);
|
||||||
|
|
||||||
// Filtros
|
const [filtroFechaDesde, setFiltroFechaDesde] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||||
const [filtroFechaDesde, setFiltroFechaDesde] = useState<string>(new Date().toISOString().split('T')[0]); //useState('');
|
const [filtroFechaHasta, setFiltroFechaHasta] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||||
const [filtroFechaHasta, setFiltroFechaHasta] = useState<string>(new Date().toISOString().split('T')[0]); //useState('');
|
|
||||||
const [filtroIdPublicacion, setFiltroIdPublicacion] = useState<number | string>('');
|
const [filtroIdPublicacion, setFiltroIdPublicacion] = useState<number | string>('');
|
||||||
const [filtroIdDistribuidor, setFiltroIdDistribuidor] = useState<number | string>('');
|
const [filtroIdDistribuidor, setFiltroIdDistribuidor] = useState<number | string>('');
|
||||||
const [filtroTipoMov, setFiltroTipoMov] = useState<'Salida' | 'Entrada' | ''>('');
|
const [filtroTipoMov, setFiltroTipoMov] = useState<'Salida' | 'Entrada' | ''>('');
|
||||||
|
|
||||||
|
|
||||||
const [publicaciones, setPublicaciones] = useState<PublicacionDto[]>([]);
|
const [publicaciones, setPublicaciones] = useState<PublicacionDto[]>([]);
|
||||||
const [distribuidores, setDistribuidores] = useState<DistribuidorDto[]>([]);
|
const [distribuidores, setDistribuidores] = useState<DistribuidorDto[]>([]);
|
||||||
const [loadingFiltersDropdown, setLoadingFiltersDropdown] = useState(false);
|
const [loadingFiltersDropdown, setLoadingFiltersDropdown] = useState(false);
|
||||||
@@ -55,6 +52,19 @@ const GestionarEntradasSalidasDistPage: React.FC = () => {
|
|||||||
const puedeVer = isSuperAdmin || tienePermiso("MD001");
|
const puedeVer = isSuperAdmin || tienePermiso("MD001");
|
||||||
const puedeGestionar = isSuperAdmin || tienePermiso("MD002");
|
const puedeGestionar = isSuperAdmin || tienePermiso("MD002");
|
||||||
|
|
||||||
|
// CORREGIDO: Función para formatear la fecha
|
||||||
|
const formatDate = (dateString?: string | null): string => {
|
||||||
|
if (!dateString) return '-';
|
||||||
|
// Asumimos que dateString viene del backend como "YYYY-MM-DD" o "YYYY-MM-DDTHH:mm:ss..."
|
||||||
|
const datePart = dateString.split('T')[0]; // Tomar solo la parte YYYY-MM-DD
|
||||||
|
const parts = datePart.split('-');
|
||||||
|
if (parts.length === 3) {
|
||||||
|
// parts[0] = YYYY, parts[1] = MM, parts[2] = DD
|
||||||
|
return `${parts[2]}/${parts[1]}/${parts[0]}`; // Formato DD/MM/YYYY
|
||||||
|
}
|
||||||
|
return datePart; // Fallback si el formato no es el esperado
|
||||||
|
};
|
||||||
|
|
||||||
const fetchFiltersDropdownData = useCallback(async () => {
|
const fetchFiltersDropdownData = useCallback(async () => {
|
||||||
setLoadingFiltersDropdown(true);
|
setLoadingFiltersDropdown(true);
|
||||||
try {
|
try {
|
||||||
@@ -126,16 +136,16 @@ const GestionarEntradasSalidasDistPage: React.FC = () => {
|
|||||||
|
|
||||||
const handleChangePage = (_event: unknown, newPage: number) => setPage(newPage);
|
const handleChangePage = (_event: unknown, newPage: number) => setPage(newPage);
|
||||||
const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setRowsPerPage(parseInt(event.target.value, 10)); setPage(0);
|
setRowsPerPage(parseInt(event.target.value, 25)); setPage(0);
|
||||||
};
|
};
|
||||||
const displayData = movimientos.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage);
|
const displayData = movimientos.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage);
|
||||||
const formatDate = (dateString?: string | null) => dateString ? new Date(dateString + 'T00:00:00Z').toLocaleDateString('es-AR') : '-';
|
// La función formatDate ya está definida arriba.
|
||||||
|
|
||||||
if (!loading && !puedeVer && !loadingFiltersDropdown) return <Box sx={{ p: 2 }}><Alert severity="error">{error || "Acceso denegado."}</Alert></Box>;
|
if (!loading && !puedeVer && !loadingFiltersDropdown) return <Box sx={{ p: 2 }}><Alert severity="error">{error || "Acceso denegado."}</Alert></Box>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 2 }}>
|
<Box sx={{ p: 1 }}>
|
||||||
<Typography variant="h4" gutterBottom>Entradas/Salidas a Distribuidores</Typography>
|
<Typography variant="h5" gutterBottom>Entradas/Salidas a Distribuidores</Typography>
|
||||||
<Paper sx={{ p: 2, mb: 2 }}>
|
<Paper sx={{ p: 2, mb: 2 }}>
|
||||||
<Typography variant="h6" gutterBottom>Filtros <FilterListIcon fontSize="small" /></Typography>
|
<Typography variant="h6" gutterBottom>Filtros <FilterListIcon fontSize="small" /></Typography>
|
||||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, alignItems: 'center', mb: 2 }}>
|
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, alignItems: 'center', mb: 2 }}>
|
||||||
@@ -187,6 +197,7 @@ const GestionarEntradasSalidasDistPage: React.FC = () => {
|
|||||||
) : (
|
) : (
|
||||||
displayData.map((m) => (
|
displayData.map((m) => (
|
||||||
<TableRow key={m.idParte} hover>
|
<TableRow key={m.idParte} hover>
|
||||||
|
{/* Usar la función formatDate aquí */}
|
||||||
<TableCell>{formatDate(m.fecha)}</TableCell>
|
<TableCell>{formatDate(m.fecha)}</TableCell>
|
||||||
<TableCell>{m.nombrePublicacion} <Chip label={m.nombreEmpresaPublicacion} size="small" variant="outlined" sx={{ ml: 0.5 }} /></TableCell>
|
<TableCell>{m.nombrePublicacion} <Chip label={m.nombreEmpresaPublicacion} size="small" variant="outlined" sx={{ ml: 0.5 }} /></TableCell>
|
||||||
<TableCell>{m.nombreDistribuidor}</TableCell>
|
<TableCell>{m.nombreDistribuidor}</TableCell>
|
||||||
@@ -196,7 +207,7 @@ const GestionarEntradasSalidasDistPage: React.FC = () => {
|
|||||||
<TableCell align="right">{m.cantidad}</TableCell>
|
<TableCell align="right">{m.cantidad}</TableCell>
|
||||||
<TableCell>{m.remito}</TableCell>
|
<TableCell>{m.remito}</TableCell>
|
||||||
<TableCell align="right" sx={{ fontWeight: 'bold', color: m.tipoMovimiento === 'Salida' ? 'success.main' : (m.montoCalculado === 0 ? 'inherit' : 'error.main') }}>
|
<TableCell align="right" sx={{ fontWeight: 'bold', color: m.tipoMovimiento === 'Salida' ? 'success.main' : (m.montoCalculado === 0 ? 'inherit' : 'error.main') }}>
|
||||||
{m.tipoMovimiento === 'Salida' ? '$'+m.montoCalculado.toFixed(2) : '$-'+m.montoCalculado.toFixed(2) }
|
{(m.tipoMovimiento === 'Salida' ? '$' : '$-') + m.montoCalculado.toLocaleString('es-AR', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell><Tooltip title={m.observacion || ''}><Box sx={{ maxWidth: 150, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{m.observacion || '-'}</Box></Tooltip></TableCell>
|
<TableCell><Tooltip title={m.observacion || ''}><Box sx={{ maxWidth: 150, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{m.observacion || '-'}</Box></Tooltip></TableCell>
|
||||||
{puedeGestionar && (
|
{puedeGestionar && (
|
||||||
|
|||||||
@@ -2,10 +2,14 @@ import React, { useState, useEffect, useCallback } from 'react';
|
|||||||
import {
|
import {
|
||||||
Box, Typography, TextField, Button, Paper, IconButton, Menu, MenuItem,
|
Box, Typography, TextField, Button, Paper, IconButton, Menu, MenuItem,
|
||||||
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TablePagination,
|
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TablePagination,
|
||||||
CircularProgress, Alert
|
CircularProgress, Alert,
|
||||||
|
ListItemIcon,
|
||||||
|
ListItemText
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
import MoreVertIcon from '@mui/icons-material/MoreVert'; // Icono para más opciones
|
||||||
|
import EditIcon from '@mui/icons-material/Edit'; // Icono para modificar
|
||||||
|
import DeleteIcon from '@mui/icons-material/Delete'; // Icono para eliminar
|
||||||
import otroDestinoService from '../../services/Distribucion/otroDestinoService';
|
import otroDestinoService from '../../services/Distribucion/otroDestinoService';
|
||||||
import type { OtroDestinoDto } from '../../models/dtos/Distribucion/OtroDestinoDto';
|
import type { OtroDestinoDto } from '../../models/dtos/Distribucion/OtroDestinoDto';
|
||||||
import type { CreateOtroDestinoDto } from '../../models/dtos/Distribucion/CreateOtroDestinoDto';
|
import type { CreateOtroDestinoDto } from '../../models/dtos/Distribucion/CreateOtroDestinoDto';
|
||||||
@@ -135,8 +139,8 @@ const GestionarOtrosDestinosPage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 2 }}>
|
<Box sx={{ p: 1 }}>
|
||||||
<Typography variant="h4" gutterBottom>Gestionar Otros Destinos</Typography>
|
<Typography variant="h5" gutterBottom>Gestionar Otros Destinos</Typography>
|
||||||
<Paper sx={{ p: 2, mb: 2 }}>
|
<Paper sx={{ p: 2, mb: 2 }}>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 2 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 2 }}>
|
||||||
<TextField
|
<TextField
|
||||||
@@ -148,11 +152,9 @@ const GestionarOtrosDestinosPage: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
{puedeCrear && (
|
{puedeCrear && (
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'flex-end', mb: 2 }}>
|
|
||||||
<Button variant="contained" startIcon={<AddIcon />} onClick={() => handleOpenModal()} sx={{ mb: 2 }}>
|
<Button variant="contained" startIcon={<AddIcon />} onClick={() => handleOpenModal()} sx={{ mb: 2 }}>
|
||||||
Agregar Nuevo Destino
|
Agregar Nuevo Destino
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
|
||||||
)}
|
)}
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
@@ -205,10 +207,16 @@ const GestionarOtrosDestinosPage: React.FC = () => {
|
|||||||
|
|
||||||
<Menu anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleMenuClose}>
|
<Menu anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleMenuClose}>
|
||||||
{puedeModificar && (
|
{puedeModificar && (
|
||||||
<MenuItem onClick={() => { handleOpenModal(selectedDestinoRow!); handleMenuClose(); }}>Modificar</MenuItem>
|
<MenuItem onClick={() => { handleOpenModal(selectedDestinoRow!); handleMenuClose(); }}>
|
||||||
|
<ListItemIcon><EditIcon fontSize="small" /></ListItemIcon>
|
||||||
|
<ListItemText>Modificar</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
{puedeEliminar && (
|
{puedeEliminar && (
|
||||||
<MenuItem onClick={() => handleDelete(selectedDestinoRow!.idDestino)}>Eliminar</MenuItem>
|
<MenuItem onClick={() => handleDelete(selectedDestinoRow!.idDestino)}>
|
||||||
|
<ListItemIcon><DeleteIcon fontSize="small" /></ListItemIcon>
|
||||||
|
<ListItemText>Eliminar</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
{(!puedeModificar && !puedeEliminar) && <MenuItem disabled>Sin acciones</MenuItem>}
|
{(!puedeModificar && !puedeEliminar) && <MenuItem disabled>Sin acciones</MenuItem>}
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|||||||
@@ -3,15 +3,26 @@ import {
|
|||||||
Box, Typography, TextField, Button, Paper, IconButton, Menu, MenuItem, Switch,
|
Box, Typography, TextField, Button, Paper, IconButton, Menu, MenuItem, Switch,
|
||||||
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TablePagination,
|
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TablePagination,
|
||||||
CircularProgress, Alert, Chip, FormControl, InputLabel, Select, Tooltip,
|
CircularProgress, Alert, Chip, FormControl, InputLabel, Select, Tooltip,
|
||||||
FormControlLabel
|
FormControlLabel,
|
||||||
|
ListItemText,
|
||||||
|
ListItemIcon
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
import MoreVertIcon from '@mui/icons-material/MoreVert'; // Icono para el menú de acciones
|
||||||
|
import EditIcon from '@mui/icons-material/Edit'; // Icono para modificar
|
||||||
|
import DeleteIcon from '@mui/icons-material/Delete'; // Icono para eliminar
|
||||||
|
import CalendarMonthIcon from '@mui/icons-material/CalendarMonth'; // Icono para días de semana
|
||||||
|
import LocalOfferIcon from '@mui/icons-material/LocalOffer'; // Para Precios
|
||||||
|
import AddCardIcon from '@mui/icons-material/AddCard'; // Para Recargos
|
||||||
|
import PercentIcon from '@mui/icons-material/Percent'; // Para Porcentajes
|
||||||
|
import RequestQuoteIcon from '@mui/icons-material/RequestQuote'; // Para Porc./Monto Canillita
|
||||||
|
import ViewQuiltIcon from '@mui/icons-material/ViewQuilt'; // Para Secciones
|
||||||
import publicacionService from '../../services/Distribucion/publicacionService';
|
import publicacionService from '../../services/Distribucion/publicacionService';
|
||||||
import type { PublicacionDto } from '../../models/dtos/Distribucion/PublicacionDto';
|
import type { PublicacionDto } from '../../models/dtos/Distribucion/PublicacionDto';
|
||||||
import type { CreatePublicacionDto } from '../../models/dtos/Distribucion/CreatePublicacionDto';
|
import type { CreatePublicacionDto } from '../../models/dtos/Distribucion/CreatePublicacionDto';
|
||||||
import type { UpdatePublicacionDto } from '../../models/dtos/Distribucion/UpdatePublicacionDto';
|
import type { UpdatePublicacionDto } from '../../models/dtos/Distribucion/UpdatePublicacionDto';
|
||||||
import PublicacionFormModal from '../../components/Modals/Distribucion/PublicacionFormModal';
|
import PublicacionFormModal from '../../components/Modals/Distribucion/PublicacionFormModal';
|
||||||
|
import PublicacionDiasSemanaModal from '../../components/Modals/Distribucion/PublicacionDiasSemanaModal';
|
||||||
import { usePermissions } from '../../hooks/usePermissions';
|
import { usePermissions } from '../../hooks/usePermissions';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
@@ -40,9 +51,11 @@ const GestionarPublicacionesPage: React.FC = () => {
|
|||||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||||
const [selectedPublicacionRow, setSelectedPublicacionRow] = useState<PublicacionDto | null>(null);
|
const [selectedPublicacionRow, setSelectedPublicacionRow] = useState<PublicacionDto | null>(null);
|
||||||
|
|
||||||
|
const [diasSemanaModalOpen, setDiasSemanaModalOpen] = useState(false);
|
||||||
|
const [selectedPublicacionParaDias, setSelectedPublicacionParaDias] = useState<PublicacionDto | null>(null);
|
||||||
|
|
||||||
const { tienePermiso, isSuperAdmin } = usePermissions();
|
const { tienePermiso, isSuperAdmin } = usePermissions();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const puedeVer = isSuperAdmin || tienePermiso("DP001");
|
const puedeVer = isSuperAdmin || tienePermiso("DP001");
|
||||||
const puedeCrear = isSuperAdmin || tienePermiso("DP002");
|
const puedeCrear = isSuperAdmin || tienePermiso("DP002");
|
||||||
const puedeModificar = isSuperAdmin || tienePermiso("DP003");
|
const puedeModificar = isSuperAdmin || tienePermiso("DP003");
|
||||||
@@ -179,6 +192,23 @@ const GestionarPublicacionesPage: React.FC = () => {
|
|||||||
handleMenuClose();
|
handleMenuClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOpenDiasSemanaModal = (publicacion: PublicacionDto) => {
|
||||||
|
setSelectedPublicacionParaDias(publicacion);
|
||||||
|
setDiasSemanaModalOpen(true);
|
||||||
|
handleMenuClose(); // Cerrar el menú de acciones si estaba abierto
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseDiasSemanaModal = () => {
|
||||||
|
setDiasSemanaModalOpen(false);
|
||||||
|
setSelectedPublicacionParaDias(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfigDiasSaved = () => {
|
||||||
|
// Opcional: Recargar publicaciones o simplemente mostrar un mensaje de éxito.
|
||||||
|
// Por ahora, solo cerramos el modal.
|
||||||
|
console.log("Configuración de días guardada.");
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
const handleChangePage = (_event: unknown, newPage: number) => setPage(newPage);
|
const handleChangePage = (_event: unknown, newPage: number) => setPage(newPage);
|
||||||
const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
@@ -191,8 +221,8 @@ const GestionarPublicacionesPage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 2 }}>
|
<Box sx={{ p: 1 }}>
|
||||||
<Typography variant="h4" gutterBottom>Gestionar Publicaciones</Typography>
|
<Typography variant="h5" gutterBottom>Gestionar Publicaciones</Typography>
|
||||||
<Paper sx={{ p: 2, mb: 2 }}>
|
<Paper sx={{ p: 2, mb: 2 }}>
|
||||||
<Box sx={{ display: 'flex', gap: 2, mb: 2, flexWrap: 'wrap', alignItems: 'center' }}>
|
<Box sx={{ display: 'flex', gap: 2, mb: 2, flexWrap: 'wrap', alignItems: 'center' }}>
|
||||||
<TextField label="Filtrar por Nombre" variant="outlined" size="small" value={filtroNombre} onChange={(e) => setFiltroNombre(e.target.value)} sx={{ flexGrow: 1, minWidth: '200px' }} />
|
<TextField label="Filtrar por Nombre" variant="outlined" size="small" value={filtroNombre} onChange={(e) => setFiltroNombre(e.target.value)} sx={{ flexGrow: 1, minWidth: '200px' }} />
|
||||||
@@ -261,17 +291,75 @@ const GestionarPublicacionesPage: React.FC = () => {
|
|||||||
</TableContainer>
|
</TableContainer>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Menu anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleMenuClose}>
|
<Menu
|
||||||
{puedeModificar && (<MenuItem onClick={() => { handleOpenModal(selectedPublicacionRow!); handleMenuClose(); }}>Modificar</MenuItem>)}
|
anchorEl={anchorEl}
|
||||||
{puedeGestionarPrecios && (<MenuItem onClick={() => handleNavigateToPrecios(selectedPublicacionRow!.idPublicacion)}>Gestionar Precios</MenuItem>)}
|
open={Boolean(anchorEl)}
|
||||||
{puedeGestionarRecargos && (<MenuItem onClick={() => handleNavigateToRecargos(selectedPublicacionRow!.idPublicacion)}>Gestionar Recargos</MenuItem>)}
|
onClose={handleMenuClose}
|
||||||
{puedeGestionarPorcDist && (<MenuItem onClick={() => handleNavigateToPorcentajesPagoDist(selectedPublicacionRow!.idPublicacion)}>Porcentajes Pago (Dist.)</MenuItem>)}
|
PaperProps={{
|
||||||
{puedeGestionarPorcCan && (<MenuItem onClick={() => handleNavigateToPorcMonCanilla(selectedPublicacionRow!.idPublicacion)}>Porc./Monto Canillita</MenuItem>)}
|
style: {
|
||||||
{puedeGestionarSecciones && (<MenuItem onClick={() => handleNavigateToSecciones(selectedPublicacionRow!.idPublicacion)}>Gestionar Secciones</MenuItem>)}
|
minWidth: 250, // Un ancho mínimo para que los textos no se corten tanto
|
||||||
{puedeEliminar && (<MenuItem onClick={() => handleDelete(selectedPublicacionRow!.idPublicacion)}>Eliminar</MenuItem>)}
|
},
|
||||||
{/* Si no hay permisos para ninguna acción */}
|
}}
|
||||||
{(!puedeModificar && !puedeEliminar && !puedeGestionarPrecios && !puedeGestionarRecargos && !puedeGestionarSecciones) &&
|
>
|
||||||
<MenuItem disabled>Sin acciones</MenuItem>}
|
{puedeModificar && selectedPublicacionRow && (
|
||||||
|
<MenuItem onClick={() => { handleOpenModal(selectedPublicacionRow); handleMenuClose(); }}>
|
||||||
|
<ListItemIcon><EditIcon fontSize="small" /></ListItemIcon>
|
||||||
|
<ListItemText>Modificar</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
{puedeGestionarPrecios && selectedPublicacionRow && (
|
||||||
|
<MenuItem onClick={() => { handleNavigateToPrecios(selectedPublicacionRow.idPublicacion); handleMenuClose(); }}>
|
||||||
|
<ListItemIcon><LocalOfferIcon fontSize="small" /></ListItemIcon>
|
||||||
|
<ListItemText>Gestionar Precios</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
{puedeGestionarRecargos && selectedPublicacionRow && (
|
||||||
|
<MenuItem onClick={() => { handleNavigateToRecargos(selectedPublicacionRow.idPublicacion); handleMenuClose(); }}>
|
||||||
|
<ListItemIcon><AddCardIcon fontSize="small" /></ListItemIcon>
|
||||||
|
<ListItemText>Gestionar Recargos</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
{puedeGestionarPorcDist && selectedPublicacionRow && (
|
||||||
|
<MenuItem onClick={() => { handleNavigateToPorcentajesPagoDist(selectedPublicacionRow.idPublicacion); handleMenuClose(); }}>
|
||||||
|
<ListItemIcon><PercentIcon fontSize="small" /></ListItemIcon>
|
||||||
|
<ListItemText>Porcentajes Pago (Dist.)</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
{puedeGestionarPorcCan && selectedPublicacionRow && (
|
||||||
|
<MenuItem onClick={() => { handleNavigateToPorcMonCanilla(selectedPublicacionRow.idPublicacion); handleMenuClose(); }}>
|
||||||
|
<ListItemIcon><RequestQuoteIcon fontSize="small" /></ListItemIcon>
|
||||||
|
<ListItemText>Porc./Monto Canillita</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
{puedeGestionarSecciones && selectedPublicacionRow && (
|
||||||
|
<MenuItem onClick={() => { handleNavigateToSecciones(selectedPublicacionRow.idPublicacion); handleMenuClose(); }}>
|
||||||
|
<ListItemIcon><ViewQuiltIcon fontSize="small" /></ListItemIcon>
|
||||||
|
<ListItemText>Gestionar Secciones</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
{puedeModificar && selectedPublicacionRow && (
|
||||||
|
<MenuItem onClick={() => { handleOpenDiasSemanaModal(selectedPublicacionRow as PublicacionDto); handleMenuClose(); }}>
|
||||||
|
<ListItemIcon><CalendarMonthIcon fontSize="small" /></ListItemIcon>
|
||||||
|
<ListItemText>Días de Salida</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
{puedeEliminar && selectedPublicacionRow && (
|
||||||
|
<MenuItem onClick={() => { handleDelete(selectedPublicacionRow.idPublicacion); handleMenuClose(); }}>
|
||||||
|
<ListItemIcon><DeleteIcon fontSize="small" /></ListItemIcon>
|
||||||
|
<ListItemText>Eliminar</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Si no hay permisos para ninguna acción y hay una fila seleccionada */}
|
||||||
|
{selectedPublicacionRow &&
|
||||||
|
!puedeModificar && !puedeEliminar &&
|
||||||
|
!puedeGestionarPrecios && !puedeGestionarRecargos &&
|
||||||
|
!puedeGestionarPorcDist && !puedeGestionarPorcCan &&
|
||||||
|
!puedeGestionarSecciones && (
|
||||||
|
<MenuItem disabled>
|
||||||
|
<ListItemText>Sin acciones disponibles</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|
||||||
<PublicacionFormModal
|
<PublicacionFormModal
|
||||||
@@ -279,6 +367,12 @@ const GestionarPublicacionesPage: React.FC = () => {
|
|||||||
initialData={editingPublicacion} errorMessage={apiErrorMessage}
|
initialData={editingPublicacion} errorMessage={apiErrorMessage}
|
||||||
clearErrorMessage={() => setApiErrorMessage(null)}
|
clearErrorMessage={() => setApiErrorMessage(null)}
|
||||||
/>
|
/>
|
||||||
|
<PublicacionDiasSemanaModal
|
||||||
|
open={diasSemanaModalOpen}
|
||||||
|
onClose={handleCloseDiasSemanaModal}
|
||||||
|
publicacion={selectedPublicacionParaDias}
|
||||||
|
onConfigSaved={handleConfigDiasSaved}
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React, { useState, useEffect, useCallback } from 'react';
|
|||||||
import {
|
import {
|
||||||
Box, Typography, TextField, Button, Paper, IconButton, Menu, MenuItem,
|
Box, Typography, TextField, Button, Paper, IconButton, Menu, MenuItem,
|
||||||
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TablePagination,
|
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TablePagination,
|
||||||
CircularProgress, Alert, FormControl, InputLabel, Select
|
CircularProgress, Alert, FormControl, InputLabel, Select, Tooltip
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||||
@@ -29,9 +29,8 @@ const GestionarSalidasOtrosDestinosPage: React.FC = () => {
|
|||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [apiErrorMessage, setApiErrorMessage] = useState<string | null>(null);
|
const [apiErrorMessage, setApiErrorMessage] = useState<string | null>(null);
|
||||||
|
|
||||||
// Filtros
|
const [filtroFechaDesde, setFiltroFechaDesde] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||||
const [filtroFechaDesde, setFiltroFechaDesde] = useState<string>(new Date().toISOString().split('T')[0]); //useState('');
|
const [filtroFechaHasta, setFiltroFechaHasta] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||||
const [filtroFechaHasta, setFiltroFechaHasta] = useState<string>(new Date().toISOString().split('T')[0]); //useState('');
|
|
||||||
const [filtroIdPublicacion, setFiltroIdPublicacion] = useState<number | string>('');
|
const [filtroIdPublicacion, setFiltroIdPublicacion] = useState<number | string>('');
|
||||||
const [filtroIdDestino, setFiltroIdDestino] = useState<number | string>('');
|
const [filtroIdDestino, setFiltroIdDestino] = useState<number | string>('');
|
||||||
|
|
||||||
@@ -48,11 +47,23 @@ const GestionarSalidasOtrosDestinosPage: React.FC = () => {
|
|||||||
const [selectedRow, setSelectedRow] = useState<SalidaOtroDestinoDto | null>(null);
|
const [selectedRow, setSelectedRow] = useState<SalidaOtroDestinoDto | null>(null);
|
||||||
|
|
||||||
const { tienePermiso, isSuperAdmin } = usePermissions();
|
const { tienePermiso, isSuperAdmin } = usePermissions();
|
||||||
// SO001, SO002 (crear/modificar), SO003 (eliminar)
|
|
||||||
const puedeVer = isSuperAdmin || tienePermiso("SO001");
|
const puedeVer = isSuperAdmin || tienePermiso("SO001");
|
||||||
const puedeCrearModificar = isSuperAdmin || tienePermiso("SO002");
|
const puedeCrearModificar = isSuperAdmin || tienePermiso("SO002");
|
||||||
const puedeEliminar = isSuperAdmin || tienePermiso("SO003");
|
const puedeEliminar = isSuperAdmin || tienePermiso("SO003");
|
||||||
|
|
||||||
|
// CORREGIDO: Función para formatear la fecha
|
||||||
|
const formatDate = (dateString?: string | null): string => {
|
||||||
|
if (!dateString) return '-';
|
||||||
|
// Asumimos que dateString viene del backend como "YYYY-MM-DD" o "YYYY-MM-DDTHH:mm:ss..."
|
||||||
|
const datePart = dateString.split('T')[0]; // Tomar solo la parte YYYY-MM-DD
|
||||||
|
const parts = datePart.split('-');
|
||||||
|
if (parts.length === 3) {
|
||||||
|
// parts[0] = YYYY, parts[1] = MM, parts[2] = DD
|
||||||
|
return `${parts[2]}/${parts[1]}/${parts[0]}`; // Formato DD/MM/YYYY
|
||||||
|
}
|
||||||
|
return datePart; // Fallback si el formato no es el esperado
|
||||||
|
};
|
||||||
|
|
||||||
const fetchFiltersDropdownData = useCallback(async () => {
|
const fetchFiltersDropdownData = useCallback(async () => {
|
||||||
setLoadingFiltersDropdown(true);
|
setLoadingFiltersDropdown(true);
|
||||||
try {
|
try {
|
||||||
@@ -140,14 +151,15 @@ const GestionarSalidasOtrosDestinosPage: React.FC = () => {
|
|||||||
const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setRowsPerPage(parseInt(event.target.value, 10)); setPage(0);
|
setRowsPerPage(parseInt(event.target.value, 10)); setPage(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
const displayData = salidas.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage);
|
const displayData = salidas.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage);
|
||||||
const formatDate = (dateString?: string | null) => dateString ? new Date(dateString + 'T00:00:00Z').toLocaleDateString('es-AR') : '-';
|
// La función formatDate ya está definida arriba.
|
||||||
|
|
||||||
if (!loading && !puedeVer && !loadingFiltersDropdown) return <Box sx={{ p: 2 }}><Alert severity="error">{error || "Acceso denegado."}</Alert></Box>;
|
if (!loading && !puedeVer && !loadingFiltersDropdown) return <Box sx={{ p: 2 }}><Alert severity="error">{error || "Acceso denegado."}</Alert></Box>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 2 }}>
|
<Box sx={{ p: 1}}>
|
||||||
<Typography variant="h4" gutterBottom>Salidas a Otros Destinos</Typography>
|
<Typography variant="h5" gutterBottom>Salidas a Otros Destinos</Typography>
|
||||||
<Paper sx={{ p: 2, mb: 2 }}>
|
<Paper sx={{ p: 2, mb: 2 }}>
|
||||||
<Typography variant="h6" gutterBottom>Filtros</Typography>
|
<Typography variant="h6" gutterBottom>Filtros</Typography>
|
||||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, alignItems: 'center', mb: 2 }}>
|
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, alignItems: 'center', mb: 2 }}>
|
||||||
@@ -186,13 +198,22 @@ const GestionarSalidasOtrosDestinosPage: React.FC = () => {
|
|||||||
</TableRow></TableHead>
|
</TableRow></TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{displayData.length === 0 ? (
|
{displayData.length === 0 ? (
|
||||||
<TableRow><TableCell colSpan={6} align="center">No se encontraron salidas.</TableCell></TableRow>
|
<TableRow><TableCell colSpan={puedeCrearModificar || puedeEliminar ? 6 : 5} align="center">No se encontraron salidas.</TableCell></TableRow>
|
||||||
) : (
|
) : (
|
||||||
displayData.map((s) => (
|
displayData.map((s) => (
|
||||||
<TableRow key={s.idParte} hover>
|
<TableRow key={s.idParte} hover>
|
||||||
<TableCell>{formatDate(s.fecha)}</TableCell><TableCell>{s.nombrePublicacion}</TableCell>
|
{/* Usar la función formatDate aquí */}
|
||||||
<TableCell>{s.nombreDestino}</TableCell><TableCell align="right">{s.cantidad}</TableCell>
|
<TableCell>{formatDate(s.fecha)}</TableCell>
|
||||||
<TableCell>{s.observacion || '-'}</TableCell>
|
<TableCell>{s.nombrePublicacion}</TableCell>
|
||||||
|
<TableCell>{s.nombreDestino}</TableCell>
|
||||||
|
<TableCell align="right">{s.cantidad}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Tooltip title={s.observacion || ''}>
|
||||||
|
<Box sx={{ maxWidth: 200, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
||||||
|
{s.observacion || '-'}
|
||||||
|
</Box>
|
||||||
|
</Tooltip>
|
||||||
|
</TableCell>
|
||||||
{(puedeCrearModificar || puedeEliminar) && (
|
{(puedeCrearModificar || puedeEliminar) && (
|
||||||
<TableCell align="right">
|
<TableCell align="right">
|
||||||
<IconButton onClick={(e) => handleMenuOpen(e, s)} disabled={!puedeCrearModificar && !puedeEliminar}><MoreVertIcon /></IconButton>
|
<IconButton onClick={(e) => handleMenuOpen(e, s)} disabled={!puedeCrearModificar && !puedeEliminar}><MoreVertIcon /></IconButton>
|
||||||
|
|||||||
@@ -2,10 +2,14 @@ import React, { useState, useEffect, useCallback } from 'react';
|
|||||||
import {
|
import {
|
||||||
Box, Typography, TextField, Button, Paper, IconButton, Menu, MenuItem,
|
Box, Typography, TextField, Button, Paper, IconButton, Menu, MenuItem,
|
||||||
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TablePagination,
|
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TablePagination,
|
||||||
CircularProgress, Alert
|
CircularProgress, Alert,
|
||||||
|
ListItemIcon,
|
||||||
|
ListItemText
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
import MoreVertIcon from '@mui/icons-material/MoreVert'; // Icono para más opciones
|
||||||
|
import EditIcon from '@mui/icons-material/Edit'; // Icono para modificar
|
||||||
|
import DeleteIcon from '@mui/icons-material/Delete'; // Icono para eliminar
|
||||||
import zonaService from '../../services/Distribucion/zonaService'; // Servicio de Zonas
|
import zonaService from '../../services/Distribucion/zonaService'; // Servicio de Zonas
|
||||||
import type { ZonaDto } from '../../models/dtos/Zonas/ZonaDto'; // DTO de Zonas
|
import type { ZonaDto } from '../../models/dtos/Zonas/ZonaDto'; // DTO de Zonas
|
||||||
import type { CreateZonaDto } from '../../models/dtos/Zonas/CreateZonaDto'; // DTOs Create
|
import type { CreateZonaDto } from '../../models/dtos/Zonas/CreateZonaDto'; // DTOs Create
|
||||||
@@ -132,8 +136,8 @@ const GestionarZonasPage: React.FC = () => {
|
|||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 2 }}>
|
<Box sx={{ p: 1 }}>
|
||||||
<Typography variant="h4" gutterBottom>
|
<Typography variant="h5" gutterBottom>
|
||||||
Gestionar Zonas
|
Gestionar Zonas
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
@@ -149,7 +153,6 @@ const GestionarZonasPage: React.FC = () => {
|
|||||||
{/* <TextField label="Filtrar por Descripción" ... /> */}
|
{/* <TextField label="Filtrar por Descripción" ... /> */}
|
||||||
</Box>
|
</Box>
|
||||||
{puedeCrear && (
|
{puedeCrear && (
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'flex-end', mb: 2 }}>
|
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
startIcon={<AddIcon />}
|
startIcon={<AddIcon />}
|
||||||
@@ -158,7 +161,6 @@ const GestionarZonasPage: React.FC = () => {
|
|||||||
>
|
>
|
||||||
Agregar Nueva Zona
|
Agregar Nueva Zona
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
|
||||||
)}
|
)}
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
@@ -218,12 +220,14 @@ const GestionarZonasPage: React.FC = () => {
|
|||||||
>
|
>
|
||||||
{puedeModificar && (
|
{puedeModificar && (
|
||||||
<MenuItem onClick={() => { handleOpenModal(selectedZonaRow!); handleMenuClose(); }}>
|
<MenuItem onClick={() => { handleOpenModal(selectedZonaRow!); handleMenuClose(); }}>
|
||||||
Modificar
|
<ListItemIcon><EditIcon fontSize="small" /></ListItemIcon>
|
||||||
|
<ListItemText>Modificar</ListItemText>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
{puedeEliminar && (
|
{puedeEliminar && (
|
||||||
<MenuItem onClick={() => handleDelete(selectedZonaRow!.idZona)}>
|
<MenuItem onClick={() => handleDelete(selectedZonaRow!.idZona)}>
|
||||||
Eliminar
|
<ListItemIcon><DeleteIcon fontSize="small" /></ListItemIcon>
|
||||||
|
<ListItemText>Eliminar</ListItemText>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
{(!puedeModificar && !puedeEliminar) && <MenuItem disabled>Sin acciones</MenuItem>}
|
{(!puedeModificar && !puedeEliminar) && <MenuItem disabled>Sin acciones</MenuItem>}
|
||||||
|
|||||||
@@ -2,10 +2,14 @@ import React, { useState, useEffect, useCallback } from 'react';
|
|||||||
import {
|
import {
|
||||||
Box, Typography, TextField, Button, Paper, IconButton, Menu, MenuItem,
|
Box, Typography, TextField, Button, Paper, IconButton, Menu, MenuItem,
|
||||||
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TablePagination,
|
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TablePagination,
|
||||||
CircularProgress, Alert
|
CircularProgress, Alert,
|
||||||
|
ListItemIcon,
|
||||||
|
ListItemText
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
import AddIcon from '@mui/icons-material/Add'; // Icono para agregar
|
||||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
import MoreVertIcon from '@mui/icons-material/MoreVert'; // Icono para más opciones
|
||||||
|
import EditIcon from '@mui/icons-material/Edit'; // Icono para modificar
|
||||||
|
import DeleteIcon from '@mui/icons-material/Delete'; // Icono para eliminar
|
||||||
import estadoBobinaService from '../../services/Impresion/estadoBobinaService';
|
import estadoBobinaService from '../../services/Impresion/estadoBobinaService';
|
||||||
import type { EstadoBobinaDto } from '../../models/dtos/Impresion/EstadoBobinaDto';
|
import type { EstadoBobinaDto } from '../../models/dtos/Impresion/EstadoBobinaDto';
|
||||||
import type { CreateEstadoBobinaDto } from '../../models/dtos/Impresion/CreateEstadoBobinaDto';
|
import type { CreateEstadoBobinaDto } from '../../models/dtos/Impresion/CreateEstadoBobinaDto';
|
||||||
@@ -132,16 +136,16 @@ const GestionarEstadosBobinaPage: React.FC = () => {
|
|||||||
|
|
||||||
if (!loading && !puedeVer) {
|
if (!loading && !puedeVer) {
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 2 }}>
|
<Box sx={{ p: 1 }}>
|
||||||
<Typography variant="h4" gutterBottom>Gestionar Estados de Bobina</Typography>
|
<Typography variant="h5" gutterBottom>Gestionar Estados de Bobina</Typography>
|
||||||
<Alert severity="error">{error || "No tiene permiso para acceder a esta sección."}</Alert>
|
<Alert severity="error">{error || "No tiene permiso para acceder a esta sección."}</Alert>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 2 }}>
|
<Box sx={{ p: 1 }}>
|
||||||
<Typography variant="h4" gutterBottom>
|
<Typography variant="h5" gutterBottom>
|
||||||
Gestionar Estados de Bobina
|
Gestionar Estados de Bobina
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
@@ -156,7 +160,6 @@ const GestionarEstadosBobinaPage: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
{puedeCrear && (
|
{puedeCrear && (
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'flex-end', mb: 2 }}>
|
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
startIcon={<AddIcon />}
|
startIcon={<AddIcon />}
|
||||||
@@ -165,7 +168,6 @@ const GestionarEstadosBobinaPage: React.FC = () => {
|
|||||||
>
|
>
|
||||||
Agregar Nuevo Estado
|
Agregar Nuevo Estado
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
|
||||||
)}
|
)}
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
@@ -226,12 +228,14 @@ const GestionarEstadosBobinaPage: React.FC = () => {
|
|||||||
>
|
>
|
||||||
{puedeModificar && (
|
{puedeModificar && (
|
||||||
<MenuItem onClick={() => { handleOpenModal(selectedEstadoRow!); handleMenuClose(); }}>
|
<MenuItem onClick={() => { handleOpenModal(selectedEstadoRow!); handleMenuClose(); }}>
|
||||||
Modificar
|
<ListItemIcon><EditIcon fontSize="small" /></ListItemIcon>
|
||||||
|
<ListItemText>Modificar</ListItemText>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
{puedeEliminar && (
|
{puedeEliminar && (
|
||||||
<MenuItem onClick={() => handleDelete(selectedEstadoRow!.idEstadoBobina)}>
|
<MenuItem onClick={() => handleDelete(selectedEstadoRow!.idEstadoBobina)}>
|
||||||
Eliminar
|
<ListItemIcon><DeleteIcon fontSize="small" /></ListItemIcon>
|
||||||
|
<ListItemText>Eliminar</ListItemText>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
{(!puedeModificar && !puedeEliminar) && <MenuItem disabled>Sin acciones</MenuItem>}
|
{(!puedeModificar && !puedeEliminar) && <MenuItem disabled>Sin acciones</MenuItem>}
|
||||||
|
|||||||
@@ -2,10 +2,14 @@ import React, { useState, useEffect, useCallback } from 'react';
|
|||||||
import {
|
import {
|
||||||
Box, Typography, TextField, Button, Paper, IconButton, Menu, MenuItem,
|
Box, Typography, TextField, Button, Paper, IconButton, Menu, MenuItem,
|
||||||
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TablePagination,
|
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TablePagination,
|
||||||
CircularProgress, Alert
|
CircularProgress, Alert,
|
||||||
|
ListItemIcon,
|
||||||
|
ListItemText
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
import AddIcon from '@mui/icons-material/Add'; // Icono para agregar
|
||||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
import MoreVertIcon from '@mui/icons-material/MoreVert'; // Icono para más opciones
|
||||||
|
import EditIcon from '@mui/icons-material/Edit'; // Icono para modificar
|
||||||
|
import DeleteIcon from '@mui/icons-material/Delete'; // Icono para eliminar
|
||||||
import plantaService from '../../services/Impresion/plantaService'; // Servicio de Plantas
|
import plantaService from '../../services/Impresion/plantaService'; // Servicio de Plantas
|
||||||
import type { PlantaDto } from '../../models/dtos/Impresion/PlantaDto';
|
import type { PlantaDto } from '../../models/dtos/Impresion/PlantaDto';
|
||||||
import type { CreatePlantaDto } from '../../models/dtos/Impresion/CreatePlantaDto';
|
import type { CreatePlantaDto } from '../../models/dtos/Impresion/CreatePlantaDto';
|
||||||
@@ -135,16 +139,16 @@ const GestionarPlantasPage: React.FC = () => {
|
|||||||
|
|
||||||
if (!loading && !puedeVer) {
|
if (!loading && !puedeVer) {
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 2 }}>
|
<Box sx={{ p: 1 }}>
|
||||||
<Typography variant="h4" gutterBottom>Gestionar Plantas de Impresión</Typography>
|
<Typography variant="h5" gutterBottom>Gestionar Plantas de Impresión</Typography>
|
||||||
<Alert severity="error">{error || "No tiene permiso para acceder a esta sección."}</Alert>
|
<Alert severity="error">{error || "No tiene permiso para acceder a esta sección."}</Alert>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 2 }}>
|
<Box sx={{ p: 1 }}>
|
||||||
<Typography variant="h4" gutterBottom>
|
<Typography variant="h5" gutterBottom>
|
||||||
Gestionar Plantas de Impresión
|
Gestionar Plantas de Impresión
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
@@ -167,7 +171,6 @@ const GestionarPlantasPage: React.FC = () => {
|
|||||||
{/* <Button variant="contained" onClick={cargarPlantas}>Buscar</Button> */}
|
{/* <Button variant="contained" onClick={cargarPlantas}>Buscar</Button> */}
|
||||||
</Box>
|
</Box>
|
||||||
{puedeCrear && (
|
{puedeCrear && (
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'flex-end', mb: 2 }}>
|
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
startIcon={<AddIcon />}
|
startIcon={<AddIcon />}
|
||||||
@@ -176,7 +179,6 @@ const GestionarPlantasPage: React.FC = () => {
|
|||||||
>
|
>
|
||||||
Agregar Nueva Planta
|
Agregar Nueva Planta
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
|
||||||
)}
|
)}
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
@@ -237,12 +239,14 @@ const GestionarPlantasPage: React.FC = () => {
|
|||||||
>
|
>
|
||||||
{puedeModificar && (
|
{puedeModificar && (
|
||||||
<MenuItem onClick={() => { handleOpenModal(selectedPlantaRow!); handleMenuClose(); }}>
|
<MenuItem onClick={() => { handleOpenModal(selectedPlantaRow!); handleMenuClose(); }}>
|
||||||
Modificar
|
<ListItemIcon><EditIcon fontSize="small" /></ListItemIcon>
|
||||||
|
<ListItemText>Modificar</ListItemText>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
{puedeEliminar && (
|
{puedeEliminar && (
|
||||||
<MenuItem onClick={() => handleDelete(selectedPlantaRow!.idPlanta)}>
|
<MenuItem onClick={() => handleDelete(selectedPlantaRow!.idPlanta)}>
|
||||||
Eliminar
|
<ListItemIcon><DeleteIcon fontSize="small" /></ListItemIcon>
|
||||||
|
<ListItemText>Eliminar</ListItemText>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
{(!puedeModificar && !puedeEliminar) && <MenuItem disabled>Sin acciones</MenuItem>}
|
{(!puedeModificar && !puedeEliminar) && <MenuItem disabled>Sin acciones</MenuItem>}
|
||||||
|
|||||||
@@ -42,8 +42,8 @@ const GestionarStockBobinasPage: React.FC = () => {
|
|||||||
const [filtroPlanta, setFiltroPlanta] = useState<number | string>('');
|
const [filtroPlanta, setFiltroPlanta] = useState<number | string>('');
|
||||||
const [filtroEstadoBobina, setFiltroEstadoBobina] = useState<number | string>('');
|
const [filtroEstadoBobina, setFiltroEstadoBobina] = useState<number | string>('');
|
||||||
const [filtroRemito, setFiltroRemito] = useState('');
|
const [filtroRemito, setFiltroRemito] = useState('');
|
||||||
const [filtroFechaDesde, setFiltroFechaDesde] = useState('');
|
const [filtroFechaDesde, setFiltroFechaDesde] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||||
const [filtroFechaHasta, setFiltroFechaHasta] = useState('');
|
const [filtroFechaHasta, setFiltroFechaHasta] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||||
|
|
||||||
// Datos para dropdowns de filtros
|
// Datos para dropdowns de filtros
|
||||||
const [tiposBobina, setTiposBobina] = useState<TipoBobinaDto[]>([]);
|
const [tiposBobina, setTiposBobina] = useState<TipoBobinaDto[]>([]);
|
||||||
@@ -178,8 +178,8 @@ const GestionarStockBobinasPage: React.FC = () => {
|
|||||||
if (!loading && !puedeVer) return <Box sx={{ p: 2 }}><Alert severity="error">{error || "Acceso denegado."}</Alert></Box>;
|
if (!loading && !puedeVer) return <Box sx={{ p: 2 }}><Alert severity="error">{error || "Acceso denegado."}</Alert></Box>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 2 }}>
|
<Box sx={{ p: 1 }}>
|
||||||
<Typography variant="h4" gutterBottom>Stock de Bobinas</Typography>
|
<Typography variant="h5" gutterBottom>Stock de Bobinas</Typography>
|
||||||
<Paper sx={{ p: 2, mb: 2 }}>
|
<Paper sx={{ p: 2, mb: 2 }}>
|
||||||
<Typography variant="h6" gutterBottom>Filtros</Typography>
|
<Typography variant="h6" gutterBottom>Filtros</Typography>
|
||||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, mb: 2}}>
|
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, mb: 2}}>
|
||||||
|
|||||||
@@ -2,10 +2,14 @@ import React, { useState, useEffect, useCallback } from 'react';
|
|||||||
import {
|
import {
|
||||||
Box, Typography, TextField, Button, Paper, IconButton, Menu, MenuItem,
|
Box, Typography, TextField, Button, Paper, IconButton, Menu, MenuItem,
|
||||||
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TablePagination,
|
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TablePagination,
|
||||||
CircularProgress, Alert
|
CircularProgress, Alert,
|
||||||
|
ListItemIcon,
|
||||||
|
ListItemText
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
import AddIcon from '@mui/icons-material/Add'; // Icono para agregar
|
||||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
import MoreVertIcon from '@mui/icons-material/MoreVert'; // Icono para más opciones
|
||||||
|
import EditIcon from '@mui/icons-material/Edit'; // Icono para modificar
|
||||||
|
import DeleteIcon from '@mui/icons-material/Delete'; // Icono para eliminar
|
||||||
import tipoBobinaService from '../../services/Impresion/tipoBobinaService'; // Servicio específico
|
import tipoBobinaService from '../../services/Impresion/tipoBobinaService'; // Servicio específico
|
||||||
import type { TipoBobinaDto } from '../../models/dtos/Impresion/TipoBobinaDto';
|
import type { TipoBobinaDto } from '../../models/dtos/Impresion/TipoBobinaDto';
|
||||||
import type { CreateTipoBobinaDto } from '../../models/dtos/Impresion/CreateTipoBobinaDto';
|
import type { CreateTipoBobinaDto } from '../../models/dtos/Impresion/CreateTipoBobinaDto';
|
||||||
@@ -132,16 +136,16 @@ const GestionarTiposBobinaPage: React.FC = () => {
|
|||||||
|
|
||||||
if (!loading && !puedeVer) {
|
if (!loading && !puedeVer) {
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 2 }}>
|
<Box sx={{ p: 1 }}>
|
||||||
<Typography variant="h4" gutterBottom>Gestionar Tipos de Bobina</Typography>
|
<Typography variant="h5" gutterBottom>Gestionar Tipos de Bobina</Typography>
|
||||||
<Alert severity="error">{error || "No tiene permiso para acceder a esta sección."}</Alert>
|
<Alert severity="error">{error || "No tiene permiso para acceder a esta sección."}</Alert>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 2 }}>
|
<Box sx={{ p: 1 }}>
|
||||||
<Typography variant="h4" gutterBottom>
|
<Typography variant="h5" gutterBottom>
|
||||||
Gestionar Tipos de Bobina
|
Gestionar Tipos de Bobina
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
@@ -157,7 +161,6 @@ const GestionarTiposBobinaPage: React.FC = () => {
|
|||||||
{/* <Button variant="contained" onClick={cargarTiposBobina}>Buscar</Button> */}
|
{/* <Button variant="contained" onClick={cargarTiposBobina}>Buscar</Button> */}
|
||||||
</Box>
|
</Box>
|
||||||
{puedeCrear && (
|
{puedeCrear && (
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'flex-end', mb: 2 }}>
|
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
startIcon={<AddIcon />}
|
startIcon={<AddIcon />}
|
||||||
@@ -166,7 +169,6 @@ const GestionarTiposBobinaPage: React.FC = () => {
|
|||||||
>
|
>
|
||||||
Agregar Nuevo Tipo
|
Agregar Nuevo Tipo
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
|
||||||
)}
|
)}
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
@@ -225,12 +227,14 @@ const GestionarTiposBobinaPage: React.FC = () => {
|
|||||||
>
|
>
|
||||||
{puedeModificar && (
|
{puedeModificar && (
|
||||||
<MenuItem onClick={() => { handleOpenModal(selectedTipoBobinaRow!); handleMenuClose(); }}>
|
<MenuItem onClick={() => { handleOpenModal(selectedTipoBobinaRow!); handleMenuClose(); }}>
|
||||||
Modificar
|
<ListItemIcon><EditIcon fontSize="small" /></ListItemIcon>
|
||||||
|
<ListItemText>Modificar</ListItemText>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
{puedeEliminar && (
|
{puedeEliminar && (
|
||||||
<MenuItem onClick={() => handleDelete(selectedTipoBobinaRow!.idTipoBobina)}>
|
<MenuItem onClick={() => handleDelete(selectedTipoBobinaRow!.idTipoBobina)}>
|
||||||
Eliminar
|
<ListItemIcon><DeleteIcon fontSize="small" /></ListItemIcon>
|
||||||
|
<ListItemText>Eliminar</ListItemText>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
{(!puedeModificar && !puedeEliminar) && <MenuItem disabled>Sin acciones</MenuItem>}
|
{(!puedeModificar && !puedeEliminar) && <MenuItem disabled>Sin acciones</MenuItem>}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ const GestionarTiradasPage: React.FC = () => {
|
|||||||
const [apiErrorMessage, setApiErrorMessage] = useState<string | null>(null);
|
const [apiErrorMessage, setApiErrorMessage] = useState<string | null>(null);
|
||||||
|
|
||||||
// Filtros
|
// Filtros
|
||||||
const [filtroFecha, setFiltroFecha] = useState<string>('');
|
const [filtroFecha, setFiltroFecha] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||||
const [filtroIdPublicacion, setFiltroIdPublicacion] = useState<number | string>('');
|
const [filtroIdPublicacion, setFiltroIdPublicacion] = useState<number | string>('');
|
||||||
const [filtroIdPlanta, setFiltroIdPlanta] = useState<number | string>('');
|
const [filtroIdPlanta, setFiltroIdPlanta] = useState<number | string>('');
|
||||||
|
|
||||||
@@ -124,8 +124,8 @@ const GestionarTiradasPage: React.FC = () => {
|
|||||||
if (!loading && !puedeVer && !loadingFiltersDropdown) return <Box sx={{ p: 2 }}><Alert severity="error">{error || "Acceso denegado."}</Alert></Box>;
|
if (!loading && !puedeVer && !loadingFiltersDropdown) return <Box sx={{ p: 2 }}><Alert severity="error">{error || "Acceso denegado."}</Alert></Box>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 2 }}>
|
<Box sx={{ p: 1 }}>
|
||||||
<Typography variant="h4" gutterBottom>Gestión de Tiradas</Typography>
|
<Typography variant="h5" gutterBottom>Gestión de Tiradas</Typography>
|
||||||
<Paper sx={{ p: 2, mb: 2 }}>
|
<Paper sx={{ p: 2, mb: 2 }}>
|
||||||
<Typography variant="h6" gutterBottom>Filtros <FilterListIcon fontSize="small"/></Typography>
|
<Typography variant="h6" gutterBottom>Filtros <FilterListIcon fontSize="small"/></Typography>
|
||||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, alignItems: 'center', mb: 2}}>
|
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, alignItems: 'center', mb: 2}}>
|
||||||
@@ -178,8 +178,8 @@ const GestionarTiradasPage: React.FC = () => {
|
|||||||
<TableContainer component={Paper} variant="outlined">
|
<TableContainer component={Paper} variant="outlined">
|
||||||
<Table size="small">
|
<Table size="small">
|
||||||
<TableHead><TableRow>
|
<TableHead><TableRow>
|
||||||
<TableCell>Sección</TableCell>
|
<TableCell><strong>Sección</strong></TableCell>
|
||||||
<TableCell align="right">Páginas</TableCell>
|
<TableCell align="right"><strong>Páginas</strong></TableCell>
|
||||||
</TableRow></TableHead>
|
</TableRow></TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{tirada.seccionesImpresas.map(sec => (
|
{tirada.seccionesImpresas.map(sec => (
|
||||||
|
|||||||
@@ -125,8 +125,8 @@ const GestionarListasRadioPage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 2 }}>
|
<Box sx={{ p: 1 }}>
|
||||||
<Typography variant="h4" gutterBottom>Generar Listas de Radio</Typography>
|
<Typography variant="h5" gutterBottom>Generar Listas de Radio</Typography>
|
||||||
<Paper sx={{ p: 3, mb: 2 }}>
|
<Paper sx={{ p: 3, mb: 2 }}>
|
||||||
<Typography variant="h6" gutterBottom>Criterios de Generación</Typography>
|
<Typography variant="h6" gutterBottom>Criterios de Generación</Typography>
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2.5 }}> {/* Aumentado el gap */}
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2.5 }}> {/* Aumentado el gap */}
|
||||||
|
|||||||
@@ -118,8 +118,8 @@ const GestionarCancionesPage: React.FC = () => {
|
|||||||
if (!loading && !puedeGestionar && !loadingFiltersDropdown) return <Box sx={{ p: 2 }}><Alert severity="error">{error || "Acceso denegado."}</Alert></Box>;
|
if (!loading && !puedeGestionar && !loadingFiltersDropdown) return <Box sx={{ p: 2 }}><Alert severity="error">{error || "Acceso denegado."}</Alert></Box>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 2 }}>
|
<Box sx={{ p: 1 }}>
|
||||||
<Typography variant="h4" gutterBottom>Gestionar Canciones</Typography>
|
<Typography variant="h5" gutterBottom>Gestionar Canciones</Typography>
|
||||||
<Paper sx={{ p: 2, mb: 2 }}>
|
<Paper sx={{ p: 2, mb: 2 }}>
|
||||||
<Typography variant="h6" gutterBottom>Filtros <FilterListIcon fontSize="small"/></Typography>
|
<Typography variant="h6" gutterBottom>Filtros <FilterListIcon fontSize="small"/></Typography>
|
||||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, alignItems: 'center', mb: 2}}>
|
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, alignItems: 'center', mb: 2}}>
|
||||||
|
|||||||
@@ -101,8 +101,8 @@ const GestionarRitmosPage: React.FC = () => {
|
|||||||
if (!loading && !puedeGestionar) return <Box sx={{ p: 2 }}><Alert severity="error">{error || "Acceso denegado."}</Alert></Box>;
|
if (!loading && !puedeGestionar) return <Box sx={{ p: 2 }}><Alert severity="error">{error || "Acceso denegado."}</Alert></Box>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 2 }}>
|
<Box sx={{ p: 1 }}>
|
||||||
<Typography variant="h4" gutterBottom>Gestionar Ritmos</Typography>
|
<Typography variant="h5" gutterBottom>Gestionar Ritmos</Typography>
|
||||||
<Paper sx={{ p: 2, mb: 2 }}>
|
<Paper sx={{ p: 2, mb: 2 }}>
|
||||||
<Typography variant="h6" gutterBottom>Filtros <FilterListIcon fontSize="small"/></Typography>
|
<Typography variant="h6" gutterBottom>Filtros <FilterListIcon fontSize="small"/></Typography>
|
||||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, alignItems: 'center', mb: 2}}>
|
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, alignItems: 'center', mb: 2}}>
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import React, { useState, useEffect, useCallback } from 'react';
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
import { useParams, useNavigate } from 'react-router-dom';
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
Box, Typography, Button, Paper, CircularProgress, Alert,
|
Box, Typography, Button, Paper, CircularProgress, Alert
|
||||||
Checkbox, FormControlLabel, FormGroup // Para el caso sin componente checklist
|
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||||
import SaveIcon from '@mui/icons-material/Save';
|
import SaveIcon from '@mui/icons-material/Save';
|
||||||
@@ -119,11 +118,11 @@ const AsignarPermisosAPerfilPage: React.FC = () => {
|
|||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 2 }}>
|
<Box sx={{ p: 1 }}>
|
||||||
<Button startIcon={<ArrowBackIcon />} onClick={() => navigate('/usuarios/perfiles')} sx={{ mb: 2 }}>
|
<Button startIcon={<ArrowBackIcon />} onClick={() => navigate('/usuarios/perfiles')} sx={{ mb: 2 }}>
|
||||||
Volver a Perfiles
|
Volver a Perfiles
|
||||||
</Button>
|
</Button>
|
||||||
<Typography variant="h4" gutterBottom>
|
<Typography variant="h5" gutterBottom>
|
||||||
Asignar Permisos al Perfil: {perfil?.nombrePerfil || 'Cargando...'}
|
Asignar Permisos al Perfil: {perfil?.nombrePerfil || 'Cargando...'}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" color="textSecondary" gutterBottom>
|
<Typography variant="body2" color="textSecondary" gutterBottom>
|
||||||
|
|||||||
@@ -42,12 +42,7 @@ const GestionarAuditoriaUsuariosPage: React.FC = () => {
|
|||||||
try {
|
try {
|
||||||
const usuariosData = await usuarioService.getAllUsuarios(); // Asumiendo que tienes este método
|
const usuariosData = await usuarioService.getAllUsuarios(); // Asumiendo que tienes este método
|
||||||
setUsuariosParaDropdown(usuariosData);
|
setUsuariosParaDropdown(usuariosData);
|
||||||
|
// Filtrar usuarios para dropdown, excluyendo los que no tienen historial
|
||||||
// Opción B para Tipos de Modificación (desde backend)
|
|
||||||
// const tiposModData = await apiClient.get<string[]>('/auditoria/tipos-modificacion'); // Ajusta el endpoint si lo creas
|
|
||||||
// setTiposModificacionParaDropdown(tiposModData.data);
|
|
||||||
|
|
||||||
// Opción A (Hardcodeado en Frontend - más simple para empezar)
|
|
||||||
setTiposModificacionParaDropdown([
|
setTiposModificacionParaDropdown([
|
||||||
"Creado", "Insertada",
|
"Creado", "Insertada",
|
||||||
"Actualizado", "Modificada",
|
"Actualizado", "Modificada",
|
||||||
@@ -136,8 +131,8 @@ const GestionarAuditoriaUsuariosPage: React.FC = () => {
|
|||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 2 }}>
|
<Box sx={{ p: 1 }}>
|
||||||
<Typography variant="h4" gutterBottom>Auditoría de Usuarios</Typography>
|
<Typography variant="h5" gutterBottom>Auditoría de Usuarios</Typography>
|
||||||
<Paper sx={{ p: 2, mb: 2 }}>
|
<Paper sx={{ p: 2, mb: 2 }}>
|
||||||
<Typography variant="h6" gutterBottom>Filtros <FilterListIcon fontSize="small" /></Typography>
|
<Typography variant="h6" gutterBottom>Filtros <FilterListIcon fontSize="small" /></Typography>
|
||||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, alignItems: 'center', mb: 2 }}>
|
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, alignItems: 'center', mb: 2 }}>
|
||||||
|
|||||||
@@ -2,17 +2,20 @@ import React, { useState, useEffect, useCallback } from 'react';
|
|||||||
import {
|
import {
|
||||||
Box, Typography, TextField, Button, Paper, IconButton, Menu, MenuItem,
|
Box, Typography, TextField, Button, Paper, IconButton, Menu, MenuItem,
|
||||||
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TablePagination,
|
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TablePagination,
|
||||||
CircularProgress, Alert, Tooltip // Añadir Tooltip
|
CircularProgress, Alert,
|
||||||
|
ListItemIcon,
|
||||||
|
ListItemText
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||||
import AssignmentIndIcon from '@mui/icons-material/AssignmentInd'; // Para asignar permisos
|
import EditIcon from '@mui/icons-material/Edit';
|
||||||
|
import DeleteIcon from '@mui/icons-material/Delete';
|
||||||
|
import LockOpenIcon from '@mui/icons-material/LockOpen';
|
||||||
import perfilService from '../../services/Usuarios/perfilService';
|
import perfilService from '../../services/Usuarios/perfilService';
|
||||||
import type { PerfilDto } from '../../models/dtos/Usuarios/PerfilDto';
|
import type { PerfilDto } from '../../models/dtos/Usuarios/PerfilDto';
|
||||||
import type { CreatePerfilDto } from '../../models/dtos/Usuarios/CreatePerfilDto';
|
import type { CreatePerfilDto } from '../../models/dtos/Usuarios/CreatePerfilDto';
|
||||||
import type { UpdatePerfilDto } from '../../models/dtos/Usuarios/UpdatePerfilDto';
|
import type { UpdatePerfilDto } from '../../models/dtos/Usuarios/UpdatePerfilDto';
|
||||||
import PerfilFormModal from '../../components/Modals/Usuarios/PerfilFormModal';
|
import PerfilFormModal from '../../components/Modals/Usuarios/PerfilFormModal';
|
||||||
// import PermisosPorPerfilModal from '../../components/Modals/PermisosPorPerfilModal'; // Lo crearemos después
|
|
||||||
import { usePermissions } from '../../hooks/usePermissions';
|
import { usePermissions } from '../../hooks/usePermissions';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { useNavigate } from 'react-router-dom'; // Para navegar
|
import { useNavigate } from 'react-router-dom'; // Para navegar
|
||||||
@@ -27,9 +30,6 @@ const GestionarPerfilesPage: React.FC = () => {
|
|||||||
const [editingPerfil, setEditingPerfil] = useState<PerfilDto | null>(null);
|
const [editingPerfil, setEditingPerfil] = useState<PerfilDto | null>(null);
|
||||||
const [apiErrorMessage, setApiErrorMessage] = useState<string | null>(null);
|
const [apiErrorMessage, setApiErrorMessage] = useState<string | null>(null);
|
||||||
|
|
||||||
// const [permisosModalOpen, setPermisosModalOpen] = useState(false); // Para modal de permisos
|
|
||||||
// const [selectedPerfilForPermisos, setSelectedPerfilForPermisos] = useState<PerfilDto | null>(null);
|
|
||||||
|
|
||||||
const [page, setPage] = useState(0);
|
const [page, setPage] = useState(0);
|
||||||
const [rowsPerPage, setRowsPerPage] = useState(5);
|
const [rowsPerPage, setRowsPerPage] = useState(5);
|
||||||
|
|
||||||
@@ -107,27 +107,10 @@ const GestionarPerfilesPage: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleOpenPermisosModal = (perfil: PerfilDto) => {
|
const handleOpenPermisosModal = (perfil: PerfilDto) => {
|
||||||
// setSelectedPerfilForPermisos(perfil);
|
|
||||||
// setPermisosModalOpen(true);
|
|
||||||
handleMenuClose();
|
handleMenuClose();
|
||||||
// Navegar a la página de asignación de permisos
|
// Navegar a la página de asignación de permisos
|
||||||
navigate(`/usuarios/perfiles/${perfil.id}/permisos`);
|
navigate(`/usuarios/perfiles/${perfil.id}/permisos`);
|
||||||
};
|
};
|
||||||
// const handleClosePermisosModal = () => {
|
|
||||||
// setPermisosModalOpen(false); setSelectedPerfilForPermisos(null);
|
|
||||||
// };
|
|
||||||
// const handleSubmitPermisos = async (idPerfil: number, permisosIds: number[]) => {
|
|
||||||
// try {
|
|
||||||
// // await perfilService.updatePermisosPorPerfil(idPerfil, permisosIds);
|
|
||||||
// // console.log("Permisos actualizados para perfil:", idPerfil);
|
|
||||||
// // Quizás un snackbar de éxito
|
|
||||||
// } catch (error) {
|
|
||||||
// console.error("Error al actualizar permisos:", error);
|
|
||||||
// setApiErrorMessage("Error al actualizar permisos.");
|
|
||||||
// }
|
|
||||||
// handleClosePermisosModal();
|
|
||||||
// };
|
|
||||||
|
|
||||||
|
|
||||||
const handleChangePage = (_event: unknown, newPage: number) => setPage(newPage);
|
const handleChangePage = (_event: unknown, newPage: number) => setPage(newPage);
|
||||||
const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
@@ -137,26 +120,24 @@ const GestionarPerfilesPage: React.FC = () => {
|
|||||||
|
|
||||||
if (!loading && !puedeVer) {
|
if (!loading && !puedeVer) {
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 2 }}>
|
<Box sx={{ p: 1 }}>
|
||||||
<Typography variant="h4" gutterBottom>Gestionar Perfiles</Typography>
|
<Typography variant="h5" gutterBottom>Gestionar Perfiles</Typography>
|
||||||
<Alert severity="error">{error || "No tiene permiso para acceder a esta sección."}</Alert>
|
<Alert severity="error">{error || "No tiene permiso para acceder a esta sección."}</Alert>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 2 }}>
|
<Box sx={{ p: 1 }}>
|
||||||
<Typography variant="h4" gutterBottom>Gestionar Perfiles</Typography>
|
<Typography variant="h5" gutterBottom>Gestionar Perfiles</Typography>
|
||||||
<Paper sx={{ p: 2, mb: 2 }}>
|
<Paper sx={{ p: 2, mb: 2 }}>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 2 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 2 }}>
|
||||||
<TextField label="Filtrar por Nombre" variant="outlined" size="small" value={filtroNombre} onChange={(e) => setFiltroNombre(e.target.value)} />
|
<TextField label="Filtrar por Nombre" variant="outlined" size="small" value={filtroNombre} onChange={(e) => setFiltroNombre(e.target.value)} />
|
||||||
</Box>
|
</Box>
|
||||||
{puedeCrear && (
|
{puedeCrear && (
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'flex-end', mb: 2 }}>
|
|
||||||
<Button variant="contained" startIcon={<AddIcon />} onClick={() => handleOpenModal()} sx={{ mb: 2 }}>
|
<Button variant="contained" startIcon={<AddIcon />} onClick={() => handleOpenModal()} sx={{ mb: 2 }}>
|
||||||
Agregar Nuevo Perfil
|
Agregar Nuevo Perfil
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
|
||||||
)}
|
)}
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
@@ -202,17 +183,44 @@ const GestionarPerfilesPage: React.FC = () => {
|
|||||||
</TableContainer>
|
</TableContainer>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Menu anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleMenuClose}>
|
<Menu
|
||||||
{puedeModificar && (
|
anchorEl={anchorEl}
|
||||||
<MenuItem onClick={() => { handleOpenModal(selectedPerfilRow!); handleMenuClose(); }}>Modificar</MenuItem>
|
open={Boolean(anchorEl)}
|
||||||
|
onClose={handleMenuClose}
|
||||||
|
PaperProps={{
|
||||||
|
style: {
|
||||||
|
minWidth: 230, // Ajusta el ancho según el texto más largo
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{puedeModificar && selectedPerfilRow && (
|
||||||
|
<MenuItem onClick={() => { handleOpenModal(selectedPerfilRow); handleMenuClose(); }}>
|
||||||
|
<ListItemIcon><EditIcon fontSize="small" /></ListItemIcon>
|
||||||
|
<ListItemText>Modificar</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
{puedeEliminar && (
|
{puedeEliminar && selectedPerfilRow && (
|
||||||
<MenuItem onClick={() => handleDelete(selectedPerfilRow!.id)}>Eliminar</MenuItem>
|
<MenuItem onClick={() => { handleDelete(selectedPerfilRow.id); handleMenuClose(); }}>
|
||||||
|
<ListItemIcon><DeleteIcon fontSize="small" /></ListItemIcon>
|
||||||
|
<ListItemText>Eliminar</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
{puedeAsignarPermisos && (
|
{puedeAsignarPermisos && selectedPerfilRow && (
|
||||||
<MenuItem onClick={() => handleOpenPermisosModal(selectedPerfilRow!)}>Asignar Permisos</MenuItem>
|
<MenuItem onClick={() => { handleOpenPermisosModal(selectedPerfilRow); handleMenuClose(); }}>
|
||||||
|
<ListItemIcon><LockOpenIcon fontSize="small" /></ListItemIcon>
|
||||||
|
<ListItemText>Asignar Permisos</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Si no hay permisos para ninguna acción y hay una fila seleccionada */}
|
||||||
|
{selectedPerfilRow &&
|
||||||
|
!puedeModificar &&
|
||||||
|
!puedeEliminar &&
|
||||||
|
!puedeAsignarPermisos && (
|
||||||
|
<MenuItem disabled>
|
||||||
|
<ListItemText>Sin acciones disponibles</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
{(!puedeModificar && !puedeEliminar && !puedeAsignarPermisos) && <MenuItem disabled>Sin acciones</MenuItem>}
|
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|
||||||
<PerfilFormModal
|
<PerfilFormModal
|
||||||
|
|||||||
@@ -2,10 +2,14 @@ import React, { useState, useEffect, useCallback } from 'react';
|
|||||||
import {
|
import {
|
||||||
Box, Typography, TextField, Button, Paper, IconButton, Menu, MenuItem,
|
Box, Typography, TextField, Button, Paper, IconButton, Menu, MenuItem,
|
||||||
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TablePagination,
|
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TablePagination,
|
||||||
CircularProgress, Alert
|
CircularProgress, Alert,
|
||||||
|
ListItemIcon,
|
||||||
|
ListItemText
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||||
|
import EditIcon from '@mui/icons-material/Edit';
|
||||||
|
import DeleteIcon from '@mui/icons-material/Delete';
|
||||||
import permisoService from '../../services/Usuarios/permisoService';
|
import permisoService from '../../services/Usuarios/permisoService';
|
||||||
import type { PermisoDto } from '../../models/dtos/Usuarios/PermisoDto';
|
import type { PermisoDto } from '../../models/dtos/Usuarios/PermisoDto';
|
||||||
import type { CreatePermisoDto } from '../../models/dtos/Usuarios/CreatePermisoDto';
|
import type { CreatePermisoDto } from '../../models/dtos/Usuarios/CreatePermisoDto';
|
||||||
@@ -101,16 +105,16 @@ const GestionarPermisosPage: React.FC = () => {
|
|||||||
|
|
||||||
if (!loading && !isSuperAdmin) {
|
if (!loading && !isSuperAdmin) {
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 2 }}>
|
<Box sx={{ p: 1 }}>
|
||||||
<Typography variant="h4" gutterBottom>Definición de Permisos</Typography>
|
<Typography variant="h5" gutterBottom>Definición de Permisos</Typography>
|
||||||
<Alert severity="error">{error || "Acceso denegado."}</Alert>
|
<Alert severity="error">{error || "Acceso denegado."}</Alert>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 2 }}>
|
<Box sx={{ p: 1 }}>
|
||||||
<Typography variant="h4" gutterBottom>Definición de Permisos (SuperAdmin)</Typography>
|
<Typography variant="h5" gutterBottom>Definición de Permisos (SuperAdmin)</Typography>
|
||||||
<Paper sx={{ p: 2, mb: 2 }}>
|
<Paper sx={{ p: 2, mb: 2 }}>
|
||||||
<Box sx={{ display: 'flex', gap: 2, mb: 2, flexWrap: 'wrap' }}>
|
<Box sx={{ display: 'flex', gap: 2, mb: 2, flexWrap: 'wrap' }}>
|
||||||
<TextField
|
<TextField
|
||||||
@@ -133,11 +137,9 @@ const GestionarPermisosPage: React.FC = () => {
|
|||||||
{/* <Button variant="contained" onClick={cargarPermisos}>Buscar</Button> */}
|
{/* <Button variant="contained" onClick={cargarPermisos}>Buscar</Button> */}
|
||||||
</Box>
|
</Box>
|
||||||
{isSuperAdmin && (
|
{isSuperAdmin && (
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'flex-end', mb: 2 }}>
|
|
||||||
<Button variant="contained" startIcon={<AddIcon />} onClick={() => handleOpenModal()} sx={{ mb: 2 }}>
|
<Button variant="contained" startIcon={<AddIcon />} onClick={() => handleOpenModal()} sx={{ mb: 2 }}>
|
||||||
Agregar Nuevo Permiso
|
Agregar Nuevo Permiso
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
|
||||||
)}
|
)}
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
@@ -184,8 +186,14 @@ const GestionarPermisosPage: React.FC = () => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<Menu anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleMenuClose}>
|
<Menu anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleMenuClose}>
|
||||||
<MenuItem onClick={() => { handleOpenModal(selectedPermisoRow!); handleMenuClose(); }}>Modificar</MenuItem>
|
<MenuItem onClick={() => { handleOpenModal(selectedPermisoRow!); handleMenuClose(); }}>
|
||||||
<MenuItem onClick={() => handleDelete(selectedPermisoRow!.id)}>Eliminar</MenuItem>
|
<ListItemIcon><EditIcon fontSize="small" /></ListItemIcon>
|
||||||
|
<ListItemText>Modificar</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem onClick={() => handleDelete(selectedPermisoRow!.id)}>
|
||||||
|
<ListItemIcon><DeleteIcon fontSize="small" /></ListItemIcon>
|
||||||
|
<ListItemText>Eliminar</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|
||||||
<PermisoFormModal
|
<PermisoFormModal
|
||||||
|
|||||||
@@ -2,10 +2,13 @@ import React, { useState, useEffect, useCallback } from 'react';
|
|||||||
import {
|
import {
|
||||||
Box, Typography, TextField, Button, Paper, IconButton, Menu, MenuItem, Switch,
|
Box, Typography, TextField, Button, Paper, IconButton, Menu, MenuItem, Switch,
|
||||||
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TablePagination,
|
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TablePagination,
|
||||||
CircularProgress, Alert, Tooltip
|
CircularProgress, Alert, Tooltip,
|
||||||
|
ListItemIcon,
|
||||||
|
ListItemText
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||||
|
import EditIcon from '@mui/icons-material/Edit';
|
||||||
import VpnKeyIcon from '@mui/icons-material/VpnKey'; // Para resetear clave
|
import VpnKeyIcon from '@mui/icons-material/VpnKey'; // Para resetear clave
|
||||||
import usuarioService from '../../services/Usuarios/usuarioService';
|
import usuarioService from '../../services/Usuarios/usuarioService';
|
||||||
import type { UsuarioDto } from '../../models/dtos/Usuarios/UsuarioDto';
|
import type { UsuarioDto } from '../../models/dtos/Usuarios/UsuarioDto';
|
||||||
@@ -141,10 +144,9 @@ const GestionarUsuariosPage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 2 }}>
|
<Box sx={{ p: 1 }}>
|
||||||
<Typography variant="h4" gutterBottom>Gestionar Usuarios</Typography>
|
<Typography variant="h5" gutterBottom>Gestionar Usuarios</Typography>
|
||||||
<Paper sx={{ p: 2, mb: 2 }}>
|
<Paper sx={{ p: 2, mb: 2 }}>
|
||||||
{/* SECCIÓN DE FILTROS CORREGIDA */}
|
|
||||||
<Box sx={{ display: 'flex', gap: 2, mb: 2, flexWrap: 'wrap' }}>
|
<Box sx={{ display: 'flex', gap: 2, mb: 2, flexWrap: 'wrap' }}>
|
||||||
<TextField
|
<TextField
|
||||||
label="Filtrar por Usuario"
|
label="Filtrar por Usuario"
|
||||||
@@ -164,11 +166,9 @@ const GestionarUsuariosPage: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
{puedeCrear && (
|
{puedeCrear && (
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'flex-end', mb: 2 }}>
|
|
||||||
<Button variant="contained" startIcon={<AddIcon />} onClick={() => handleOpenUsuarioModal()} sx={{ mb: 2 }}>
|
<Button variant="contained" startIcon={<AddIcon />} onClick={() => handleOpenUsuarioModal()} sx={{ mb: 2 }}>
|
||||||
Agregar Nuevo Usuario
|
Agregar Nuevo Usuario
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
|
||||||
)}
|
)}
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
@@ -231,7 +231,10 @@ const GestionarUsuariosPage: React.FC = () => {
|
|||||||
|
|
||||||
<Menu anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleMenuClose}>
|
<Menu anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleMenuClose}>
|
||||||
{(puedeModificar || puedeAsignarPerfil) && (
|
{(puedeModificar || puedeAsignarPerfil) && (
|
||||||
<MenuItem onClick={() => { handleOpenUsuarioModal(selectedUsuarioRow!); handleMenuClose(); }}>Modificar</MenuItem>
|
<MenuItem onClick={() => { handleOpenUsuarioModal(selectedUsuarioRow!); handleMenuClose(); }}>
|
||||||
|
<ListItemIcon><EditIcon fontSize="small" /></ListItemIcon>
|
||||||
|
<ListItemText>Modificar</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
{puedeResetearClave && selectedUsuarioRow && currentUser?.userId !== selectedUsuarioRow.id && (
|
{puedeResetearClave && selectedUsuarioRow && currentUser?.userId !== selectedUsuarioRow.id && (
|
||||||
<MenuItem onClick={() => handleOpenSetPasswordModal(selectedUsuarioRow!)}>
|
<MenuItem onClick={() => handleOpenSetPasswordModal(selectedUsuarioRow!)}>
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import apiClient from '../apiClient';
|
|||||||
import type { PublicacionDto } from '../../models/dtos/Distribucion/PublicacionDto';
|
import type { PublicacionDto } from '../../models/dtos/Distribucion/PublicacionDto';
|
||||||
import type { CreatePublicacionDto } from '../../models/dtos/Distribucion/CreatePublicacionDto';
|
import type { CreatePublicacionDto } from '../../models/dtos/Distribucion/CreatePublicacionDto';
|
||||||
import type { UpdatePublicacionDto } from '../../models/dtos/Distribucion/UpdatePublicacionDto';
|
import type { UpdatePublicacionDto } from '../../models/dtos/Distribucion/UpdatePublicacionDto';
|
||||||
|
import type { PublicacionDiaSemanaDto } from '../../models/dtos/Distribucion/PublicacionDiaSemanaDto';
|
||||||
|
import type { UpdatePublicacionDiasSemanaRequestDto } from '../../models/dtos/Distribucion/UpdatePublicacionDiasSemanaRequestDto';
|
||||||
|
|
||||||
const getAllPublicaciones = async (
|
const getAllPublicaciones = async (
|
||||||
nombreFilter?: string,
|
nombreFilter?: string,
|
||||||
@@ -35,12 +37,29 @@ const deletePublicacion = async (id: number): Promise<void> => {
|
|||||||
await apiClient.delete(`/publicaciones/${id}`);
|
await apiClient.delete(`/publicaciones/${id}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getConfiguracionDiasPublicacion = async (idPublicacion: number): Promise<PublicacionDiaSemanaDto[]> => {
|
||||||
|
const response = await apiClient.get<PublicacionDiaSemanaDto[]>(`/publicaciones/${idPublicacion}/dias-semana`);
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateConfiguracionDiasPublicacion = async (idPublicacion: number, data: UpdatePublicacionDiasSemanaRequestDto): Promise<void> => {
|
||||||
|
await apiClient.put(`/publicaciones/${idPublicacion}/dias-semana`, data);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPublicacionesPorDiaSemana = async (diaSemana: number): Promise<PublicacionDto[]> => {
|
||||||
|
const response = await apiClient.get<PublicacionDto[]>('/publicaciones/por-dia-semana', { params: { dia: diaSemana } });
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
const publicacionService = {
|
const publicacionService = {
|
||||||
getAllPublicaciones,
|
getAllPublicaciones,
|
||||||
getPublicacionById,
|
getPublicacionById,
|
||||||
createPublicacion,
|
createPublicacion,
|
||||||
updatePublicacion,
|
updatePublicacion,
|
||||||
deletePublicacion,
|
deletePublicacion,
|
||||||
|
getConfiguracionDiasPublicacion,
|
||||||
|
updateConfiguracionDiasPublicacion,
|
||||||
|
getPublicacionesPorDiaSemana
|
||||||
};
|
};
|
||||||
|
|
||||||
export default publicacionService;
|
export default publicacionService;
|
||||||
@@ -366,6 +366,25 @@ const getControlDevolucionesPdf = async (params: {
|
|||||||
return response.data;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getTicketLiquidacionCanillaPdf = async (params: {
|
||||||
|
fecha: string; // YYYY-MM-DD
|
||||||
|
idCanilla: number;
|
||||||
|
esAccionista?: boolean; // Hacerlo opcional, el backend podría tener un default
|
||||||
|
}): Promise<Blob> => {
|
||||||
|
const queryParams: Record<string, string | number | boolean> = {
|
||||||
|
fecha: params.fecha,
|
||||||
|
idCanilla: params.idCanilla,
|
||||||
|
};
|
||||||
|
if (params.esAccionista !== undefined) {
|
||||||
|
queryParams.esAccionista = params.esAccionista;
|
||||||
|
}
|
||||||
|
const response = await apiClient.get('/reportes/ticket-liquidacion-canilla/pdf', {
|
||||||
|
params: queryParams,
|
||||||
|
responseType: 'blob',
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
const reportesService = {
|
const reportesService = {
|
||||||
getExistenciaPapel,
|
getExistenciaPapel,
|
||||||
getExistenciaPapelPdf,
|
getExistenciaPapelPdf,
|
||||||
@@ -401,6 +420,7 @@ const reportesService = {
|
|||||||
getListadoDistribucionDistribuidoresPdf,
|
getListadoDistribucionDistribuidoresPdf,
|
||||||
getControlDevolucionesData,
|
getControlDevolucionesData,
|
||||||
getControlDevolucionesPdf,
|
getControlDevolucionesPdf,
|
||||||
|
getTicketLiquidacionCanillaPdf,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default reportesService;
|
export default reportesService;
|
||||||
Reference in New Issue
Block a user