sales & grn fixers

This commit is contained in:
2026-10-04 15:31:59 +05:30
parent cea433e320
commit 3d3dbbbb59
52 changed files with 20761 additions and 482 deletions
@@ -23,8 +23,9 @@ public sealed class CustomersController : ApiControllerBase
[FromQuery] PageQuery query,
[FromQuery] EntityStatus? status,
[FromQuery] CustomerType? customerType,
CancellationToken ct)
=> Ok(await _customers.ListAsync(query, status, customerType, ct));
CancellationToken ct,
[FromQuery] bool includeCashCustomer = false)
=> Ok(await _customers.ListAsync(query, status, customerType, includeCashCustomer, ct));
[HttpGet("{customerId:int}")]
[ProducesResponseType(typeof(CustomerDto), StatusCodes.Status200OK)]
@@ -0,0 +1,45 @@
using Microsoft.AspNetCore.Authorization;
using ERPCore.Infra.Auth;
using ERPCore.Dtos.Config;
using ERPCore.Services.Interfaces;
using Microsoft.AspNetCore.Mvc;
namespace ERPCore.Controllers;
/// <summary>
/// GRN configuration endpoints — warehouse selection and the default receiving warehouse.
/// <para>
/// <b>Authorization:</b> gated on the Receiving nav policy (the New GRN form has to read it), same
/// Phase-1 posture as <see cref="SalesConfigController"/>.
/// </para>
/// </summary>
[Route("api/v1/grn-config")]
[Authorize(Policy = NavPolicies.Prefix + "receiving")]
public sealed class GrnConfigController : ApiControllerBase
{
private readonly IGrnConfigService _config;
public GrnConfigController(IGrnConfigService config) => _config = config;
[HttpGet]
[ProducesResponseType(typeof(GrnConfigDto), StatusCodes.Status200OK)]
public async Task<ActionResult<GrnConfigDto>> Get(CancellationToken ct)
{
var result = await _config.GetAsync(ct);
SetETag(result.RowVersion);
return Ok(result.Value);
}
[HttpPut]
[ProducesResponseType(typeof(GrnConfigDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status412PreconditionFailed)]
public async Task<ActionResult<GrnConfigDto>> Update(
[FromBody] UpdateGrnConfigRequest request, CancellationToken ct)
{
var expected = RequireIfMatch();
var result = await _config.UpdateAsync(request, expected, ct);
SetETag(result.RowVersion);
return Ok(result.Value);
}
}
@@ -51,6 +51,16 @@ public sealed class SalesInvoicesController : ApiControllerBase
return Ok(new SalesStockAvailabilityDto(itemId, warehouseId, isStocked, available));
}
// Feeds New Sale's "Add item" dropdown with each item's issuable stock at the sale's warehouse.
// Under "sales" for the same reason as above; items absent from the list have no stock there.
[HttpGet("stock-summary")]
[ProducesResponseType(typeof(IReadOnlyList<SalesItemStockDto>), StatusCodes.Status200OK)]
public async Task<ActionResult<IReadOnlyList<SalesItemStockDto>>> StockSummary([FromQuery] int warehouseId, CancellationToken ct)
{
var available = await _stock.GetAvailableByItemAsync(warehouseId, ct);
return Ok(available.Select(kv => new SalesItemStockDto(kv.Key, kv.Value)).ToList());
}
// Lets the New Sale form pre-fill Unit price for items with no flat selling price, using the
// same resolution the server applies on save.
[HttpGet("suggested-price")]
@@ -143,6 +153,18 @@ public sealed class SalesInvoicesController : ApiControllerBase
return Created($"/api/v1/sales-invoices/{salesInvoiceId}/payments/{dto.SalesInvoicePaymentId}", dto);
}
/// <summary>
/// Pay several Posted invoices of one (non-cash) customer at once, split across payment lines;
/// applied oldest invoice first under a single GL journal entry. Returns one payment per split.
/// </summary>
[HttpPost("payments/bulk")]
[ProducesResponseType(typeof(IReadOnlyList<SalesInvoicePaymentDto>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status409Conflict)]
public async Task<ActionResult<IReadOnlyList<SalesInvoicePaymentDto>>> PayBulk([FromBody] CreateBulkSalesInvoicePaymentRequest request, CancellationToken ct)
=> Ok(await _payments.PayBulkAsync(request, ct));
/// <summary>Payment history for this invoice, newest first.</summary>
[HttpGet("{salesInvoiceId:int}/payments")]
[ProducesResponseType(typeof(IReadOnlyList<SalesInvoicePaymentDto>), StatusCodes.Status200OK)]