diff --git a/deploy/pos-publish.sh b/deploy/pos-publish.sh index 674c550..1a0259c 100755 --- a/deploy/pos-publish.sh +++ b/deploy/pos-publish.sh @@ -42,14 +42,18 @@ build_appimage() { cp -r "$src/." "$appdir/usr/bin/" cat > "$appdir/foodmarket-pos.desktop" < "$appdir/foodmarket-pos.png" + # Фирменная иконка ALL MARKET (зелёный квадрат 512). Фоллбэк — 1x1 PNG. + if [ -f src/food-market.pos/Assets/allmarket-icon-512.png ]; then + cp src/food-market.pos/Assets/allmarket-icon-512.png "$appdir/foodmarket-pos.png" + else + printf '\x89PNG\r\n\x1a\n' > "$appdir/foodmarket-pos.png" + fi cat > "$appdir/AppRun" <<'RUN' #!/bin/sh HERE="$(dirname "$(readlink -f "$0")")" diff --git a/docs/pos-design/pos-mockup.html b/docs/pos-design/pos-mockup.html index 7b47de5..7a09e15 100644 --- a/docs/pos-design/pos-mockup.html +++ b/docs/pos-design/pos-mockup.html @@ -18,8 +18,9 @@ /* верхняя полоса */ .top{background:var(--card);border-bottom:1px solid var(--line);display:flex;align-items:center;gap:24px;padding:0 24px} - .logo{font-weight:800;font-size:20px;color:var(--green);letter-spacing:.2px} - .logo span{color:var(--text);font-weight:600} + .logo{font-weight:500;font-size:22px;color:var(--green);letter-spacing:-.5px} + .logo b{color:#4C4F54;font-weight:300} + .logo span{color:var(--muted);font-weight:600;font-size:17px;margin-left:4px} .sep{width:1px;height:28px;background:var(--line)} .meta{color:var(--muted);font-size:15px} .meta b{color:var(--text);font-weight:600} @@ -125,7 +126,7 @@
- +
Магазин Демо-магазин · Точка Касса 1
diff --git a/src/food-market.pos.core/Auth/TokenStore.cs b/src/food-market.pos.core/Auth/TokenStore.cs index 5979d9d..4b871b2 100644 --- a/src/food-market.pos.core/Auth/TokenStore.cs +++ b/src/food-market.pos.core/Auth/TokenStore.cs @@ -9,6 +9,8 @@ public sealed class PosSession public required string AccessToken { get; set; } public required string RefreshToken { get; set; } public string? CashierName { get; set; } + /// Название точки/магазина — для шапки чека (резолвится из RetailPoint). + public string? StoreName { get; set; } public Guid? CashierUserId { get; set; } public string[]? Roles { get; set; } public Guid? RetailPointId { get; set; } diff --git a/src/food-market.pos/Assets/allmarket-icon-512.png b/src/food-market.pos/Assets/allmarket-icon-512.png new file mode 100644 index 0000000..d9d620a Binary files /dev/null and b/src/food-market.pos/Assets/allmarket-icon-512.png differ diff --git a/src/food-market.pos/Assets/allmarket-logo-color.png b/src/food-market.pos/Assets/allmarket-logo-color.png new file mode 100644 index 0000000..086814e Binary files /dev/null and b/src/food-market.pos/Assets/allmarket-logo-color.png differ diff --git a/src/food-market.pos/Assets/allmarket-logo-white.png b/src/food-market.pos/Assets/allmarket-logo-white.png new file mode 100644 index 0000000..a2e4f0f Binary files /dev/null and b/src/food-market.pos/Assets/allmarket-logo-white.png differ diff --git a/src/food-market.pos/Assets/allmarket.ico b/src/food-market.pos/Assets/allmarket.ico new file mode 100644 index 0000000..51be300 Binary files /dev/null and b/src/food-market.pos/Assets/allmarket.ico differ diff --git a/src/food-market.pos/Services/CheckoutService.cs b/src/food-market.pos/Services/CheckoutService.cs index dcb7fb2..52474f5 100644 --- a/src/food-market.pos/Services/CheckoutService.cs +++ b/src/food-market.pos/Services/CheckoutService.cs @@ -96,7 +96,7 @@ public sealed record CommitResult(Guid ClientSaleId, bool PushedNow, int Pending IReadOnlyList<(string Name, string Unit)> display, decimal refundCash, decimal refundCard) { var doc = new ReceiptDoc { Title = "ВОЗВРАТ" }; - doc.Add("Food Market", center: true, bold: true); + doc.Add(_svc.Session?.StoreName ?? "Магазин", center: true, bold: true); if (CurrentShift?.Number is { } sn) doc.Add($"Смена {sn}", center: true); if (_svc.Session?.CashierName is { } c) doc.Add($"Кассир: {c}"); doc.Add(DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss")); @@ -117,6 +117,7 @@ public sealed record CommitResult(Guid ClientSaleId, bool PushedNow, int Pending if (refundCash > 0m) doc.Add("Наличными", $"{refundCash:N0}"); if (refundCard > 0m) doc.Add("На карту", $"{refundCard:N0}"); doc.Separator(); + doc.Add("ALL MARKET — система учёта торговли", center: true); return doc; } @@ -124,7 +125,7 @@ private ReceiptDoc BuildReceipt(Cart cart, decimal paidCash, decimal paidCard, d { var t = cart.Totals(); var doc = new ReceiptDoc { Title = isReturn ? "ВОЗВРАТ" : "ТОВАРНЫЙ ЧЕК" }; - doc.Add("Food Market", center: true, bold: true); + doc.Add(_svc.Session?.StoreName ?? "Магазин", center: true, bold: true); if (CurrentShift?.Number is { } sn) doc.Add($"Смена {sn}", center: true); if (_svc.Session?.CashierName is { } c) doc.Add($"Кассир: {c}"); doc.Add(DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss")); @@ -145,6 +146,7 @@ private ReceiptDoc BuildReceipt(Cart cart, decimal paidCash, decimal paidCard, d if (t.Vat > 0m) doc.Add("в т.ч. НДС", $"{t.Vat:N0}"); doc.Separator(); doc.Add("Спасибо за покупку!", center: true); + doc.Add("ALL MARKET — система учёта торговли", center: true); return doc; } } diff --git a/src/food-market.pos/ViewModels/ViewModels.cs b/src/food-market.pos/ViewModels/ViewModels.cs index d337056..afd8ea5 100644 --- a/src/food-market.pos/ViewModels/ViewModels.cs +++ b/src/food-market.pos/ViewModels/ViewModels.cs @@ -61,6 +61,7 @@ private void StartSession() { CashierName = _svc!.Session?.CashierName ?? "кассир"; QueueCount = _svc.Store.PendingCount(); + _ = ResolvePointNameAsync(); _sale = new SaleViewModel(_svc) { PaymentRequested = OpenPayment, SaleCommitted = _ => RefreshStatus(), @@ -72,6 +73,25 @@ private void StartSession() _ = CheckForUpdateAsync(); } + /// Резолвит название точки продаж (для шапки чека и верхней полосы). + private async Task ResolvePointNameAsync() + { + var pid = _svc?.Session?.RetailPointId; + if (_svc?.Api is null || pid is null) return; + try + { + var points = await _svc.Api.RetailPointsAsync(); + var name = points.items.FirstOrDefault(p => p.id == pid.Value)?.name; + if (!string.IsNullOrWhiteSpace(name)) + { + PointName = name; + StoreName = name; + if (_svc.Session is not null) _svc.Session.StoreName = name; + } + } + catch { /* офлайн — оставляем значения по умолчанию */ } + } + private async Task CheckForUpdateAsync() { if (_svc?.Api is null) return; diff --git a/src/food-market.pos/Views/Brand.cs b/src/food-market.pos/Views/Brand.cs new file mode 100644 index 0000000..a5468ea --- /dev/null +++ b/src/food-market.pos/Views/Brand.cs @@ -0,0 +1,42 @@ +using System; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Media.Imaging; +using Avalonia.Platform; +using Avalonia.Styling; + +namespace foodmarket.Pos.Views; + +/// Логотип и иконка ALL MARKET из ресурсов (avares). Логотип подбирается +/// под тему: color на светлой, white на тёмной. Файлы готовые — не перерисовывать. +public static class Brand +{ + private const string Pkg = "avares://foodmarket.Pos/Assets/"; + + private static bool IsDark => + Application.Current?.ActualThemeVariant == ThemeVariant.Dark; + + /// Загрузить логотип (под текущую тему) в переданный Image. + public static void ApplyLogo(Image? target) + { + if (target is null) return; + var file = IsDark ? "allmarket-logo-white.png" : "allmarket-logo-color.png"; + try + { + using var s = AssetLoader.Open(new Uri(Pkg + file)); + target.Source = new Bitmap(s); + } + catch { /* ресурс недоступен — оставить пустым */ } + } + + /// Поставить иконку окна/приложения. + public static void ApplyWindowIcon(Window window) + { + try + { + using var s = AssetLoader.Open(new Uri(Pkg + "allmarket.ico")); + window.Icon = new WindowIcon(s); + } + catch { /* ресурс недоступен */ } + } +} diff --git a/src/food-market.pos/Views/LoginView.axaml b/src/food-market.pos/Views/LoginView.axaml index f614cb5..6521626 100644 --- a/src/food-market.pos/Views/LoginView.axaml +++ b/src/food-market.pos/Views/LoginView.axaml @@ -6,12 +6,9 @@ - - - - - - + + + diff --git a/src/food-market.pos/Views/LoginView.axaml.cs b/src/food-market.pos/Views/LoginView.axaml.cs index e5a418f..73e631c 100644 --- a/src/food-market.pos/Views/LoginView.axaml.cs +++ b/src/food-market.pos/Views/LoginView.axaml.cs @@ -4,5 +4,9 @@ namespace foodmarket.Pos.Views; public partial class LoginView : UserControl { - public LoginView() => InitializeComponent(); + public LoginView() + { + InitializeComponent(); + Brand.ApplyLogo(this.FindControl("BrandLogo")); + } } diff --git a/src/food-market.pos/Views/MainWindow.axaml b/src/food-market.pos/Views/MainWindow.axaml index 26582af..d6a59de 100644 --- a/src/food-market.pos/Views/MainWindow.axaml +++ b/src/food-market.pos/Views/MainWindow.axaml @@ -4,7 +4,7 @@ x:Class="foodmarket.Pos.Views.MainWindow" x:DataType="vm:MainWindowViewModel" Width="1366" Height="768" MinWidth="1100" MinHeight="680" - Background="{DynamicResource PosBg}" Title="Касса food-market" + Background="{DynamicResource PosBg}" Title="ALL MARKET · Касса" WindowStartupLocation="CenterScreen"> @@ -12,10 +12,11 @@ - - - - + + + + + diff --git a/src/food-market.pos/Views/MainWindow.axaml.cs b/src/food-market.pos/Views/MainWindow.axaml.cs index 3b28ea1..034d427 100644 --- a/src/food-market.pos/Views/MainWindow.axaml.cs +++ b/src/food-market.pos/Views/MainWindow.axaml.cs @@ -11,6 +11,8 @@ public MainWindow() { InitializeComponent(); AddHandler(KeyDownEvent, OnKeyDown, Avalonia.Interactivity.RoutingStrategies.Tunnel); + Brand.ApplyWindowIcon(this); + Brand.ApplyLogo(this.FindControl("BrandLogo")); // Адаптив шапки по ширине: при нехватке места сокращаем тексты чипов и // прячем «Магазин …», но чипы статуса никогда не обрезаем. this.GetObservable(BoundsProperty).Subscribe(new BoundsObserver(_ => ApplyTopBarDensity())); diff --git a/src/food-market.pos/food-market.pos.csproj b/src/food-market.pos/food-market.pos.csproj index 37751b9..35a3a6b 100644 --- a/src/food-market.pos/food-market.pos.csproj +++ b/src/food-market.pos/food-market.pos.csproj @@ -7,9 +7,14 @@ enable enable true - + Assets\allmarket.ico + + + + +