Finalización de Reportes y arreglos varios de controles y comportamientos...

This commit is contained in:
2025-06-03 13:45:20 -03:00
parent 99532b03f1
commit 062cc05fd0
67 changed files with 4523 additions and 993 deletions

View File

@@ -83,7 +83,10 @@ namespace GestionIntegral.Api.Controllers.Distribucion
public async Task<IActionResult> UpdateMovimiento(int idParte, [FromBody] UpdateEntradaSalidaCanillaDto updateDto)
{
if (!TienePermiso(PermisoModificarMovimiento)) return Forbid();
if (!ModelState.IsValid) return BadRequest(ModelState);
// Esta línea es la que dispara la validación del modelo 'updateDto'
if (!ModelState.IsValid) return BadRequest(ModelState);
var userId = GetCurrentUserId();
if (userId == null) return Unauthorized();
@@ -91,7 +94,7 @@ namespace GestionIntegral.Api.Controllers.Distribucion
if (!exito)
{
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 NoContent();

View File

@@ -117,5 +117,65 @@ namespace GestionIntegral.Api.Controllers.Distribucion
}
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);
}
}
}

View File

@@ -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: " &amp; 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>

View File

@@ -1384,5 +1384,78 @@ namespace GestionIntegral.Api.Controllers
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.");
}
}
}
}

View File

@@ -263,16 +263,14 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
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.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 sqlHistorico = @"
INSERT INTO dbo.dist_EntradasSalidasCanillas_H
(Id_Parte, Id_Publicacion, Id_Canilla, Fecha, CantSalida, CantEntrada, Id_Precio, Id_Recargo, Id_PorcMon, Observacion, Id_Usuario, FechaMod, TipoMod)
VALUES (@IdParteHist, @IdPubHist, @IdCanillaHist, @FechaHist, @CantSalidaHist, @CantEntradaHist, @IdPrecioHist, @IdRecargoHist, @IdPorcMonHist, @ObsHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
INSERT INTO dbo.dist_EntradasSalidasCanillas_H
(Id_Parte, Id_Publicacion, Id_Canilla, Fecha, CantSalida, CantEntrada, Id_Precio, Id_Recargo, Id_PorcMon, Observacion, Id_Usuario, FechaMod, TipoMod)
VALUES (@IdParteHist, @IdPubHist, @IdCanillaHist, @FechaHist, @CantSalidaHist, @CantEntradaHist, @IdPrecioHist, @IdRecargoHist, @IdPorcMonHist, @ObsHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
await transaction.Connection!.ExecuteAsync(sqlHistorico, new
{

View File

@@ -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> ExistsByNameAndEmpresaAsync(string nombre, int idEmpresa, int? excludeIdPublicacion = null);
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);
}
}

View File

@@ -106,8 +106,8 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
WHERE Id_Publicacion = @IdParam";
try
{
using var connection = _connectionFactory.CreateConnection();
return await connection.QuerySingleOrDefaultAsync<Publicacion>(sql, new { IdParam = id });
using var connection = _connectionFactory.CreateConnection();
return await connection.QuerySingleOrDefaultAsync<Publicacion>(sql, new { IdParam = id });
}
catch (Exception ex)
{
@@ -134,7 +134,7 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
}
catch (Exception ex)
{
_logger.LogError(ex, "Error en ExistsByNameAndEmpresaAsync. Nombre: {Nombre}, IdEmpresa: {IdEmpresa}", nombre, idEmpresa);
_logger.LogError(ex, "Error en ExistsByNameAndEmpresaAsync. Nombre: {Nombre}, IdEmpresa: {IdEmpresa}", nombre, idEmpresa);
return true; // Asumir que existe en caso de error para prevenir duplicados
}
}
@@ -176,7 +176,7 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
INSERT INTO dbo.dist_dtPublicaciones (Nombre, Observacion, Id_Empresa, CtrlDevoluciones, Habilitada)
OUTPUT INSERTED.Id_Publicacion AS IdPublicacion, INSERTED.Nombre, INSERTED.Observacion, INSERTED.Id_Empresa AS IdEmpresa, INSERTED.CtrlDevoluciones, INSERTED.Habilitada
VALUES (@Nombre, @Observacion, @IdEmpresa, @CtrlDevoluciones, @Habilitada);";
var connection = transaction.Connection!;
var inserted = await connection.QuerySingleAsync<Publicacion>(sqlInsert, nuevaPublicacion, transaction);
if (inserted == null || inserted.IdPublicacion == 0) throw new DataException("Error al crear la publicación o al obtener el ID generado.");
@@ -192,7 +192,7 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
NombreParam = inserted.Nombre,
ObservacionParam = inserted.Observacion,
IdEmpresaParam = inserted.IdEmpresa,
HabilitadaParam = inserted.Habilitada ?? true,
HabilitadaParam = inserted.Habilitada ?? true,
IdUsuarioParam = idUsuario, // Renombrado para claridad con el parámetro de la función
FechaModParam = DateTime.Now,
TipoModParam = "Creada"
@@ -267,5 +267,65 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
var rowsAffected = await connection.ExecuteAsync(sqlDelete, new { IdParam = id }, transaction);
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.
}
}
}

View File

@@ -41,5 +41,7 @@ namespace GestionIntegral.Api.Data.Repositories.Reportes
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<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);
}
}

View File

@@ -479,5 +479,41 @@ namespace GestionIntegral.Api.Data.Repositories.Reportes
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>();
}
}
}
}

View File

@@ -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; }
}
}

View File

@@ -1,38 +1,56 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
namespace GestionIntegral.Api.Dtos.Distribucion
{
[CustomValidation(typeof(CreateBulkEntradaSalidaCanillaDto), nameof(ValidateNoDuplicatePublicationsInItems))]
public class CreateBulkEntradaSalidaCanillaDto
{
[Required(ErrorMessage = "El ID del canillita es obligatorio.")]
public int IdCanilla { get; set; }
[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.")]
[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>();
// Validar que no haya publicaciones duplicadas en la lista de items
[CustomValidation(typeof(CreateBulkEntradaSalidaCanillaDto), nameof(ValidateNoDuplicatePublications))]
public string? DuplicateError { get; set; }
public static ValidationResult? ValidateNoDuplicatePublications(CreateBulkEntradaSalidaCanillaDto dto, ValidationContext context)
public static ValidationResult? ValidateNoDuplicatePublicationsInItems(CreateBulkEntradaSalidaCanillaDto instanceToValidate, ValidationContext context)
{
if (dto.Items != null)
if (instanceToValidate == null)
{
var duplicatePublications = dto.Items
.GroupBy(item => item.IdPublicacion)
return new ValidationResult("El objeto principal de la solicitud es nulo.");
}
if (instanceToValidate.Items == null)
{
return ValidationResult.Success; // O error si una lista nula de items es inválida
}
if (instanceToValidate.Items.Any())
{
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)
.Where(group => group.Count() > 1)
.Select(group => group.Key)
.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;

View File

@@ -3,9 +3,11 @@ using System.ComponentModel.DataAnnotations;
namespace GestionIntegral.Api.Dtos.Distribucion
{
[CustomValidation(typeof(EntradaSalidaCanillaItemDto), nameof(ValidateCantidadesItem))] // Aplicar a nivel de clase
public class EntradaSalidaCanillaItemDto
{
[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; }
[Required(ErrorMessage = "La cantidad de salida es obligatoria.")]
@@ -17,17 +19,26 @@ namespace GestionIntegral.Api.Dtos.Distribucion
public int CantEntrada { get; set; }
[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
[CustomValidation(typeof(EntradaSalidaCanillaItemDto), nameof(ValidateCantidades))]
public string? CantidadesError { get; set; }
// Ya no necesitamos la propiedad CantidadesError para esta validación
// 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;
}

View File

@@ -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
}
}

View File

@@ -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;
namespace GestionIntegral.Api.Dtos.Distribucion
{
// Aplicar el CustomValidation a nivel de clase
[CustomValidation(typeof(UpdateEntradaSalidaCanillaDto), nameof(ValidateCantidades))]
public class UpdateEntradaSalidaCanillaDto
{
[Required, Range(0, int.MaxValue)]
public int CantSalida { get; set; }
[Required, Range(0, int.MaxValue)]
public int CantEntrada { get; set; }
[StringLength(150)]
public string? Observacion { get; set; }
[CustomValidation(typeof(UpdateEntradaSalidaCanillaDto), nameof(ValidateCantidades))]
public string? CantidadesError { get; set; } // Dummy para validación
public static ValidationResult? ValidateCantidades(UpdateEntradaSalidaCanillaDto dto, ValidationContext context)
// El método de validación ahora recibe la instancia completa del DTO
public static ValidationResult? ValidateCantidades(UpdateEntradaSalidaCanillaDto instanceToValidate, ValidationContext context)
{
if (dto.CantEntrada > dto.CantSalida)
if (instanceToValidate == null)
{
return new ValidationResult("La cantidad de entrada no puede ser mayor a la de salida.");
// No debería ocurrir si el model binding funcionó, pero es una buena práctica.
return ValidationResult.Success;
}
if (instanceToValidate.CantEntrada > instanceToValidate.CantSalida)
{
// Asociar el error a las propiedades relevantes si es posible y útil
return new ValidationResult(
"La cantidad de entrada no puede ser mayor a la de salida.",
new[] { nameof(CantEntrada), nameof(CantSalida) } // Opcional: nombres de miembros
);
}
return ValidationResult.Success;
}

View File

@@ -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>();
}
}

View File

@@ -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; }
}

View File

@@ -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'
}

View File

@@ -222,10 +222,10 @@ namespace GestionIntegral.Api.Services.Distribucion
{
if (connection is System.Data.Common.DbConnection dbConnOpen && connection.State == ConnectionState.Closed) await dbConnOpen.OpenAsync();
else if (connection.State == ConnectionState.Closed) connection.Open();
transaction = connection.BeginTransaction();
var esExistente = await _esCanillaRepository.GetByIdAsync(idParte);
var esExistente = await _esCanillaRepository.GetByIdAsync(idParte); // Obtener el estado actual
if (esExistente == null)
{
if (transaction?.Connection != null) transaction.Rollback();
@@ -234,17 +234,16 @@ namespace GestionIntegral.Api.Services.Distribucion
if (esExistente.Liquidado)
{
// Permiso MC006 es para "Eliminar Movimientos de Canillita Liquidados"
if (!TienePermisoEspecifico("MC006"))
if (!TienePermisoEspecifico("MC006")) // <--- AQUÍ ESTÁ LA VERIFICACIÓN
{
if (transaction?.Connection != null) transaction.Rollback();
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);
}
// 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)
{
// No es necesario hacer rollback aquí si DeleteAsync lanza una excepción,
@@ -258,10 +257,10 @@ namespace GestionIntegral.Api.Services.Distribucion
_logger.LogInformation("Movimiento Canillita ID {IdParte} eliminado por Usuario ID {IdUsuario}.", idParte, idUsuario);
return (true, null);
}
catch (KeyNotFoundException)
{
catch (KeyNotFoundException)
{
if (transaction?.Connection != null) try { transaction.Rollback(); } catch (Exception exR) { _logger.LogError(exR, "Rollback fallido KeyNotFoundException."); }
return (false, "Movimiento no encontrado.");
return (false, "Movimiento no encontrado.");
}
catch (Exception ex)
{

View File

@@ -11,5 +11,8 @@ namespace GestionIntegral.Api.Services.Distribucion
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)> 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);
}
}

View File

@@ -202,5 +202,71 @@ namespace GestionIntegral.Api.Services.Distribucion
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}");
}
}
}
}

View File

@@ -63,5 +63,11 @@ namespace GestionIntegral.Api.Services.Reportes
)> 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<LiquidacionCanillaDetalleDto> Detalles,
IEnumerable<LiquidacionCanillaGananciaDto> Ganancias,
string? Error
)> ObtenerDatosTicketLiquidacionAsync(DateTime fecha, int idCanilla);
}
}

View File

@@ -449,5 +449,46 @@ namespace GestionIntegral.Api.Services.Reportes
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."
);
}
}
}
}

View File

@@ -6,7 +6,7 @@
}
},
"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": {
"Key": "badb1a38d221c9e23bcf70958840ca7f5a5dc54f2047dadf7ce45b578b5bc3e2",

View File

@@ -6,7 +6,7 @@
}
},
"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": {
"Key": "badb1a38d221c9e23bcf70958840ca7f5a5dc54f2047dadf7ce45b578b5bc3e2",

View File

@@ -13,7 +13,7 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("GestionIntegral.Api")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[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.AssemblyTitleAttribute("GestionIntegral.Api")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]

View File

@@ -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":{}}

View File

@@ -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":{}}

View File

@@ -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":{}}