Сервер уже слал остатки в sync (StockSyncDto), но касса их отбрасывала. Теперь: - PosStore хранит снимок остатков (таблица stock, перезапись каждый синк — сервер шлёт полный снимок ненулевых); GetStock/NegativeCount/NegativeStocks. - В строке чека при количестве больше остатка — «на остатке N шт — уйдёт в минус» (жёлтое, НЕ блокирует продажу — решение владельца). Работает офлайн по снимку. - В шапке — постоянный жёлтый индикатор «N в минусе — нужна инвентаризация» по складу кассы, по тапу — список товаров (Flyout). Обновляется при старте, после чека и после синка. - Тесты PosStore: снимок/перезапись, GetStock, NegativeCount/список (4/4). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
102 lines
8 KiB
XML
102 lines
8 KiB
XML
<Window xmlns="https://github.com/avaloniaui"
|
||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||
xmlns:vm="clr-namespace:foodmarket.Pos.ViewModels"
|
||
x:Class="foodmarket.Pos.Views.MainWindow"
|
||
x:DataType="vm:MainWindowViewModel"
|
||
Width="1366" Height="768" MinWidth="1100" MinHeight="680"
|
||
Background="{DynamicResource PosBg}" Title="ALL MARKET · Касса"
|
||
WindowStartupLocation="CenterScreen">
|
||
<Panel>
|
||
<Grid RowDefinitions="64,*,40">
|
||
|
||
<!-- Верхняя полоса -->
|
||
<Border Grid.Row="0" Background="{DynamicResource PosCard}" BorderBrush="{DynamicResource PosBorder}" BorderThickness="0,0,0,1" Padding="24,0">
|
||
<Grid ColumnDefinitions="Auto,Auto,*,Auto" VerticalAlignment="Center">
|
||
<!-- Лого ALL MARKET (готовый файл; color на светлой теме, white на тёмной).
|
||
Высота 48 → ширина ≈ 140 px (≥ минимума брендбука), охранное поле — Spacing. -->
|
||
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="16" VerticalAlignment="Center">
|
||
<Image x:Name="BrandLogo" Height="48" VerticalAlignment="Center"/>
|
||
<Border Width="1" Height="26" Background="{DynamicResource PosBorder}" VerticalAlignment="Center"/>
|
||
<TextBlock Text="Касса" FontSize="17" Foreground="{DynamicResource PosTextMuted}" FontWeight="SemiBold" VerticalAlignment="Center"/>
|
||
</StackPanel>
|
||
|
||
<!-- Мета (сворачивается по ширине) -->
|
||
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="24" VerticalAlignment="Center" Margin="24,0,0,0">
|
||
<StackPanel x:Name="StoreMeta" Orientation="Horizontal" Spacing="24" VerticalAlignment="Center">
|
||
<TextBlock VerticalAlignment="Center" FontSize="15" Foreground="{DynamicResource PosTextMuted}">
|
||
<Run Text="{Binding StoreName}" Foreground="{DynamicResource PosText}" FontWeight="SemiBold"/><Run Text=" · Точка "/><Run Text="{Binding PointName}" Foreground="{DynamicResource PosText}" FontWeight="SemiBold"/>
|
||
</TextBlock>
|
||
<Border Width="1" Height="28" Background="{DynamicResource PosBorder}"/>
|
||
</StackPanel>
|
||
<TextBlock VerticalAlignment="Center" FontSize="15" Foreground="{DynamicResource PosTextMuted}">
|
||
<Run Text="Кассир "/><Run Text="{Binding CashierName}" Foreground="{DynamicResource PosText}" FontWeight="SemiBold"/>
|
||
</TextBlock>
|
||
</StackPanel>
|
||
|
||
<!-- Сообщение об обновлении (в растяжимой зоне) -->
|
||
<TextBlock Grid.Column="2" VerticalAlignment="Center" Margin="24,0" FontSize="13" Foreground="{DynamicResource PosWarn}" Text="{Binding UpdateMessage}"
|
||
TextTrimming="CharacterEllipsis" IsVisible="{Binding UpdateMessage, Converter={x:Static ObjectConverters.IsNotNull}}"/>
|
||
|
||
<!-- Чипы статуса (всегда, никогда не обрезаются) -->
|
||
<StackPanel Grid.Column="3" Orientation="Horizontal" Spacing="10" VerticalAlignment="Center">
|
||
<!-- Постоянный жёлтый индикатор «N товаров в минусе» (по складу кассы).
|
||
По нажатию — список во всплывашке. Виден только при наличии минусов. -->
|
||
<Button Padding="12,6" CornerRadius="999" BorderThickness="0"
|
||
Background="#F59E0B" Foreground="#1F2937"
|
||
IsVisible="{Binding HasNegatives}" Command="{Binding ToggleNegativeListCommand}"
|
||
ToolTip.Tip="Товары в минусе — нужна инвентаризация">
|
||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||
<TextBlock Text="⚠" VerticalAlignment="Center"/>
|
||
<TextBlock Text="{Binding NegativeBadge}" VerticalAlignment="Center" FontWeight="SemiBold"/>
|
||
</StackPanel>
|
||
<Button.Flyout>
|
||
<Flyout Placement="Bottom">
|
||
<ScrollViewer MaxHeight="420" MaxWidth="380">
|
||
<TextBlock Text="{Binding NegativeListText}" TextWrapping="Wrap" FontSize="13"/>
|
||
</ScrollViewer>
|
||
</Flyout>
|
||
</Button.Flyout>
|
||
</Button>
|
||
<Border Classes="chip" Classes.ok="{Binding IsShiftOpen}" Classes.warn="{Binding !IsShiftOpen}">
|
||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||
<Ellipse Width="8" Height="8" VerticalAlignment="Center" Fill="{DynamicResource PosBrand}"/>
|
||
<TextBlock Text="{Binding ShiftBadge}"/>
|
||
</StackPanel>
|
||
</Border>
|
||
<Border Classes="chip net">
|
||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||
<Ellipse Width="8" Height="8" VerticalAlignment="Center" Fill="{DynamicResource PosBrand}"/>
|
||
<TextBlock Text="{Binding NetBadge}"/>
|
||
<TextBlock Text="{Binding QueueBadge}" IsVisible="{Binding QueueBadge, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||
</StackPanel>
|
||
</Border>
|
||
</StackPanel>
|
||
</Grid>
|
||
</Border>
|
||
|
||
<!-- Текущая страница -->
|
||
<ContentControl Grid.Row="1" Content="{Binding CurrentPage}"/>
|
||
|
||
<!-- Полоса горячих клавиш -->
|
||
<Border Grid.Row="2" Background="{DynamicResource PosCard}" BorderBrush="{DynamicResource PosBorder}" BorderThickness="0,1,0,0" Padding="24,0">
|
||
<StackPanel Orientation="Horizontal" Spacing="28" VerticalAlignment="Center" HorizontalAlignment="Left">
|
||
<StackPanel Orientation="Horizontal" Spacing="8"><Border Classes="kbd"><TextBlock Text="F1"/></Border><TextBlock Text="Помощь" FontSize="14" Foreground="{DynamicResource PosTextMuted}" VerticalAlignment="Center"/></StackPanel>
|
||
<StackPanel Orientation="Horizontal" Spacing="8"><Border Classes="kbd"><TextBlock Text="F2"/></Border><TextBlock Text="Поиск" FontSize="14" Foreground="{DynamicResource PosTextMuted}" VerticalAlignment="Center"/></StackPanel>
|
||
<StackPanel Orientation="Horizontal" Spacing="8"><Border Classes="kbd"><TextBlock Text="F4"/></Border><TextBlock Text="Скидка" FontSize="14" Foreground="{DynamicResource PosTextMuted}" VerticalAlignment="Center"/></StackPanel>
|
||
<StackPanel Orientation="Horizontal" Spacing="8"><Border Classes="kbd"><TextBlock Text="F7"/></Border><TextBlock Text="Отложить" FontSize="14" Foreground="{DynamicResource PosTextMuted}" VerticalAlignment="Center"/></StackPanel>
|
||
<StackPanel Orientation="Horizontal" Spacing="8"><Border Classes="kbd"><TextBlock Text="F9"/></Border><TextBlock Text="Возврат" FontSize="14" Foreground="{DynamicResource PosTextMuted}" VerticalAlignment="Center"/></StackPanel>
|
||
<StackPanel Orientation="Horizontal" Spacing="8"><Border Classes="kbd"><TextBlock Text="F10"/></Border><TextBlock Text="Смена" FontSize="14" Foreground="{DynamicResource PosTextMuted}" VerticalAlignment="Center"/></StackPanel>
|
||
<StackPanel Orientation="Horizontal" Spacing="8"><Border Classes="kbd"><TextBlock Text="F12"/></Border><TextBlock Text="Оплата" FontSize="14" Foreground="{DynamicResource PosTextMuted}" VerticalAlignment="Center"/></StackPanel>
|
||
<StackPanel Orientation="Horizontal" Spacing="8"><Border Classes="kbd"><TextBlock Text="Del"/></Border><TextBlock Text="Удалить строку" FontSize="14" Foreground="{DynamicResource PosTextMuted}" VerticalAlignment="Center"/></StackPanel>
|
||
<StackPanel Orientation="Horizontal" Spacing="8"><Border Classes="kbd"><TextBlock Text="Esc"/></Border><TextBlock Text="Отмена" FontSize="14" Foreground="{DynamicResource PosTextMuted}" VerticalAlignment="Center"/></StackPanel>
|
||
</StackPanel>
|
||
</Border>
|
||
</Grid>
|
||
|
||
<!-- Модальная оплата -->
|
||
<Border Background="{DynamicResource PosScrim}" IsVisible="{Binding IsPaymentOpen}">
|
||
<ContentControl Content="{Binding Payment}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||
</Border>
|
||
</Panel>
|
||
</Window>
|