feat(pos): быстрое создание товара по неизвестному штрихкоду из кассы (п.4)
Скан неизвестного штрихкода (онлайн) → диалог «Новый товар» (наименование + опц. цена) → POST /products/quick → товар сразу в чеке; попадёт в локальный каталог на ближайшем синке. Офлайн — как было («не найден»). Smoke/uitest зелёные. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
cfc335e911
commit
8c4a9c4e5e
|
|
@ -24,6 +24,11 @@ public interface IServerApi
|
|||
[Get("/api/me")]
|
||||
Task<MeDto> MeAsync(CancellationToken ct = default);
|
||||
|
||||
/// <summary>Быстрое создание товара по неизвестному штрихкоду прямо из кассы
|
||||
/// (имя + опц. цена). Только онлайн.</summary>
|
||||
[Post("/api/catalog/products/quick")]
|
||||
Task<QuickCreatedProductDto> QuickCreateProductAsync([Body] QuickCreateProductBody body, CancellationToken ct = default);
|
||||
|
||||
[Get("/api/catalog/retail-points")]
|
||||
Task<PagedDto<RetailPointDto>> RetailPointsAsync([Query] int pageSize = 200, CancellationToken ct = default);
|
||||
|
||||
|
|
@ -46,6 +51,13 @@ public interface IServerApi
|
|||
Task<ShiftReportDto> ShiftReportAsync(Guid id, CancellationToken ct = default);
|
||||
}
|
||||
|
||||
public record QuickCreateProductBody(string Name, string? Barcode, decimal? SalePrice, bool Weighed = false);
|
||||
public record QuickBarcodeDto(string code);
|
||||
public record QuickPriceDto(decimal amount);
|
||||
public record QuickCreatedProductDto(Guid id, string name, string unitName, int packaging,
|
||||
System.Collections.Generic.IReadOnlyList<QuickBarcodeDto>? barcodes,
|
||||
System.Collections.Generic.IReadOnlyList<QuickPriceDto>? prices);
|
||||
|
||||
public record MeDto(Guid? orgId, string[] roles, bool advancedMode, string? name, string? email, string sub,
|
||||
Dictionary<string, bool>? permissions = null, Guid[]? retailPointIds = null);
|
||||
public record PagedDto<T>(IReadOnlyList<T> items, int total);
|
||||
|
|
|
|||
|
|
@ -524,6 +524,13 @@ public partial class SaleViewModel : ViewModelBase
|
|||
partial void OnItemCountChanged(int value) => OnPropertyChanged(nameof(IsCartEmpty));
|
||||
partial void OnDiscountChanged(decimal value) => OnPropertyChanged(nameof(HasAnyDiscount));
|
||||
|
||||
// ── Быстрое создание товара по неизвестному штрихкоду (онлайн) ────────────
|
||||
[ObservableProperty] private bool _isQuickCreateOpen;
|
||||
[ObservableProperty] private string _quickCreateBarcode = "";
|
||||
[ObservableProperty] private string _quickCreateName = "";
|
||||
[ObservableProperty] private decimal? _quickCreatePrice;
|
||||
[ObservableProperty] private bool _quickCreateBusy;
|
||||
|
||||
/// <summary>Поднять модальную оплату (обрабатывает оболочка).</summary>
|
||||
public Action<PaymentViewModel>? PaymentRequested;
|
||||
/// <summary>Чек проведён — оболочка обновляет индикатор очереди.</summary>
|
||||
|
|
@ -594,12 +601,58 @@ private void Scan()
|
|||
if (raw.Length == 0) return;
|
||||
var parsed = BarcodeParser.Parse(raw);
|
||||
var p = _svc?.Store.FindByBarcode(parsed.Lookup);
|
||||
if (p is null) { Message = $"Штрихкод {parsed.Lookup} не найден"; return; }
|
||||
if (p is null)
|
||||
{
|
||||
// Неизвестный штрихкод: онлайн — предлагаем быстро создать товар
|
||||
// (1 поле + цена), офлайн — просто сообщаем.
|
||||
if (_svc?.Api is not null)
|
||||
{
|
||||
QuickCreateBarcode = parsed.Lookup;
|
||||
QuickCreateName = "";
|
||||
QuickCreatePrice = null;
|
||||
IsQuickCreateOpen = true;
|
||||
Message = $"Штрихкод {parsed.Lookup} не найден — создайте товар";
|
||||
}
|
||||
else Message = $"Штрихкод {parsed.Lookup} не найден";
|
||||
return;
|
||||
}
|
||||
var qty = parsed.IsWeighted && parsed.EmbeddedWeight is { } w ? w : 1m;
|
||||
var price = parsed.IsWeighted && parsed.EmbeddedPrice is { } pr ? pr / Math.Max(0.001m, qty) : (decimal?)null;
|
||||
AddProduct(p, qty, price);
|
||||
}
|
||||
|
||||
[RelayCommand] private void QuickCreateCancel() { IsQuickCreateOpen = false; QuickCreateName = ""; QuickCreatePrice = null; }
|
||||
|
||||
/// <summary>Создать товар по неизвестному штрихкоду на сервере и сразу добавить в чек.</summary>
|
||||
[RelayCommand]
|
||||
private async Task QuickCreateConfirmAsync()
|
||||
{
|
||||
if (_svc?.Api is null || QuickCreateBusy) return;
|
||||
if (string.IsNullOrWhiteSpace(QuickCreateName)) { Message = "Укажите название товара"; return; }
|
||||
QuickCreateBusy = true;
|
||||
try
|
||||
{
|
||||
var dto = await _svc.Api.QuickCreateProductAsync(
|
||||
new QuickCreateProductBody(QuickCreateName.Trim(), QuickCreateBarcode, QuickCreatePrice));
|
||||
var np = new PosProduct
|
||||
{
|
||||
Id = dto.id, Name = dto.name, Barcodes = new[] { QuickCreateBarcode },
|
||||
UnitCode = string.IsNullOrWhiteSpace(dto.unitName) ? "шт" : dto.unitName,
|
||||
Packaging = (PosPackaging)(dto.packaging == 0 ? 1 : dto.packaging),
|
||||
VatPercent = 12m, VatEnabled = true,
|
||||
Price = dto.prices is { Count: > 0 } pr ? pr[0].amount : (QuickCreatePrice ?? 0m),
|
||||
};
|
||||
IsQuickCreateOpen = false;
|
||||
AddProduct(np, 1m);
|
||||
// Товар появится в локальном каталоге при ближайшем синке.
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Message = "Не удалось создать товар: " + ex.Message;
|
||||
}
|
||||
finally { QuickCreateBusy = false; }
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void RemoveSelected()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -202,5 +202,23 @@
|
|||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Быстрое создание товара по неизвестному штрихкоду (онлайн) -->
|
||||
<Border Grid.ColumnSpan="2" Background="#99000000" IsVisible="{Binding IsQuickCreateOpen}">
|
||||
<Border Width="440" Background="{DynamicResource PosCard}" CornerRadius="14" Padding="24"
|
||||
VerticalAlignment="Center" HorizontalAlignment="Center" BorderBrush="{DynamicResource PosBorder}" BorderThickness="1">
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock Text="Новый товар" FontSize="20" FontWeight="SemiBold" Foreground="{DynamicResource PosText}"/>
|
||||
<TextBlock Text="{Binding QuickCreateBarcode, StringFormat='Штрихкод: {0}'}" Foreground="{DynamicResource PosTextMuted}"/>
|
||||
<TextBox Watermark="Наименование" FontSize="16" Text="{Binding QuickCreateName}"/>
|
||||
<TextBox Watermark="Цена продажи (можно позже)" FontSize="16" Text="{Binding QuickCreatePrice}"/>
|
||||
<StackPanel Orientation="Horizontal" Spacing="12" HorizontalAlignment="Right">
|
||||
<Button Content="Отмена" Padding="18,10" Command="{Binding QuickCreateCancelCommand}"/>
|
||||
<Button Content="Создать и добавить" Padding="18,10" Background="{DynamicResource PosBrand}" Foreground="White"
|
||||
Command="{Binding QuickCreateConfirmCommand}" IsEnabled="{Binding !QuickCreateBusy}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Border>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
|
|
|||
Loading…
Reference in a new issue