Werksverkauf:
-Preisänderungen ausblenden für Teststellung
This commit is contained in:
parent
c8689880b6
commit
62014e4b63
@ -0,0 +1,26 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace WerksverkaufScanner.Data
|
||||
{
|
||||
[Table("ArtikelFilialPreise", Schema = "dbo")]
|
||||
public class ArtikelFilialPreise
|
||||
{
|
||||
public int ArtikelId { get; set; }
|
||||
[Key]
|
||||
public int ArtikelVariantenId { get; set; }
|
||||
public int Variante { get; set; }
|
||||
public string ArtikelNummer { get; set; } = "";
|
||||
public string Barcode { get; set; } = "";
|
||||
public string Bezeichnung { get; set; } = "";
|
||||
public string Bontext { get; set; } = "";
|
||||
public decimal? NettoGewicht { get; set; }
|
||||
public string Mengeneinheit { get; set; }
|
||||
public decimal? BasisMenge { get; set; }
|
||||
public string AS400ArtikelNummer { get; set; } = "";
|
||||
public string? Farbe { get; set; }
|
||||
public string? Text { get; set; }
|
||||
public decimal? Verkaufspreis { get; set; }
|
||||
public int FilialId { get; set; }
|
||||
}
|
||||
}
|
||||
@ -11,4 +11,6 @@ public class ScannerDb : DbContext
|
||||
public DbSet<Kassierer> Kassierer { get; set; } = null!;
|
||||
public DbSet<PreisAenderung> PreisAenderungen => Set<PreisAenderung>();
|
||||
public DbSet<Filiale> Filialen => Set<Filiale>();
|
||||
public DbSet<ArtikelFilialPreise> ArtikelFilialPreise { get; set; } = null!;
|
||||
|
||||
}
|
||||
|
||||
@ -28,7 +28,7 @@ else
|
||||
Bei Fragen oder Problemen wenden Sie sich bitte an <br /><br />
|
||||
<strong>Herrn Meinhold</strong> (📞 0175 / 35 31 462, ✉️ <a href="mailto:christopher.meinhold@lambertz.com">christopher.meinhold@lambertz.com</a>)<br />
|
||||
oder<br />
|
||||
<strong>Herrn Vossel</strong> (📞 02405 / 40804 188, ✉️ <a href="mailto:ruediger.vossel@lambertz.com">ruediger.vossel@lambertz.com</a>).
|
||||
<strong>Herrn Vossel</strong> (📞 02405 / 40804 188, ✉️ <a href="mailto:ruediger.vossel@lambertz.com">ruediger.vossel@lambertz.com</a>)
|
||||
</div>
|
||||
|
||||
@code {
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
@* <div class="col">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body text-center">
|
||||
<i class="bi bi-gear display-5 text-primary mb-2"></i>
|
||||
@ -27,7 +27,7 @@
|
||||
<a href="/stammdaten" class="btn btn-primary">Öffnen</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> *@
|
||||
|
||||
<div class="col">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
|
||||
@ -2,35 +2,36 @@
|
||||
@attribute [Authorize]
|
||||
|
||||
@using System.Globalization
|
||||
|
||||
@using WerksverkaufScanner.Data
|
||||
@using WerksverkaufScanner.Services
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
|
||||
@inject StammdatenCache Cache
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
|
||||
@inject IDbContextFactory<ScannerDb> DbFactory
|
||||
@inject PreisAenderungSqlService Preise
|
||||
@inject IJSRuntime JS
|
||||
|
||||
<h2>Preisänderung</h2>
|
||||
|
||||
<!-- 1) Barcode ODER Artikelnummer -->
|
||||
<input @ref="barcodeRef" id="barcode"
|
||||
class="form-control form-control-lg mb-2"
|
||||
placeholder="Barcode scannen oder Artikelnummer eingeben"
|
||||
@bind="scanText" @bind:event="oninput"
|
||||
@onkeydown="OnScanKey" />
|
||||
|
||||
@if (varianten is { Count: > 1 })
|
||||
@if (varianten?.Count > 1)
|
||||
{
|
||||
<div class="mb-3">
|
||||
<div class="mb-1">Varianten gefunden – bitte auswählen:</div>
|
||||
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
@foreach (var art in varianten)
|
||||
{
|
||||
var isActive = ReferenceEquals(gefunden, art);
|
||||
<button type="button"
|
||||
class="btn btn-lg"
|
||||
style="@GetButtonStyle(art, isActive)"
|
||||
style="@(isActive ? "border:2px solid #007bff;background:#e9f7ff;" : "")"
|
||||
@onclick="@(() => VarianteAuswaehlen(art))">
|
||||
@GetVarianteLabel(art)
|
||||
</button>
|
||||
@ -57,15 +58,16 @@
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 2) Preis-Eingabe (masked: 599 -> 5,99) -->
|
||||
<!-- Preis-Eingabe -->
|
||||
<div class="input-group input-group-lg mb-2">
|
||||
<span class="input-group-text">€</span>
|
||||
<input id="preis"
|
||||
<input @ref="priceRef" id="preis"
|
||||
class="form-control text-end"
|
||||
inputmode="numeric" pattern="[0-9]*"
|
||||
placeholder="z. B. 599 → 5,99"
|
||||
@bind="priceText" @bind:event="oninput" @bind:after="OnPriceChanged"
|
||||
@onkeydown="OnPriceKey" />
|
||||
<span class="input-group-text text-muted">z. B. 599 → 5,99</span>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary"
|
||||
@ -81,25 +83,32 @@
|
||||
}
|
||||
|
||||
@code {
|
||||
// Eingaben/Status
|
||||
private string? scanText;
|
||||
private Artikel? gefunden;
|
||||
private List<Artikel>? varianten;
|
||||
private ArtikelFilialPreise? gefunden;
|
||||
private List<ArtikelFilialPreise>? varianten;
|
||||
|
||||
private string? priceText; // angezeigter Text ("5,99")
|
||||
private int? priceCents; // intern in Cent (599)
|
||||
private string? priceText;
|
||||
private int? priceCents;
|
||||
private string? status;
|
||||
private bool ok;
|
||||
|
||||
// Fokussteuerung
|
||||
private ElementReference barcodeRef, priceRef;
|
||||
private bool focusBarcode = true;
|
||||
private bool focusPrice;
|
||||
|
||||
private int? filialId; // Aktuelle Filiale
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
if (Cache.GetArtikel().Count == 0)
|
||||
await Cache.RefreshAsync();
|
||||
// Filial-ID holen (async, weil localStorage)
|
||||
var filialIdStr = await JS.InvokeAsync<string?>("localStorage.getItem", "scannerpilot.filialId");
|
||||
if (!string.IsNullOrWhiteSpace(filialIdStr) && int.TryParse(filialIdStr.Trim('"'), out var id))
|
||||
filialId = id;
|
||||
else
|
||||
{
|
||||
status = "Keine Filiale ausgewählt – bitte zuerst konfigurieren!";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
@ -113,10 +122,7 @@
|
||||
if (focusPrice)
|
||||
{
|
||||
focusPrice = false;
|
||||
await JS.InvokeVoidAsync("setTimeout", (Action)(async () =>
|
||||
{
|
||||
await JS.InvokeVoidAsync("document.getElementById", "preis");
|
||||
}), 0);
|
||||
await priceRef.FocusAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@ -145,22 +151,23 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if (Cache.GetArtikel().Count == 0)
|
||||
await Cache.RefreshAsync();
|
||||
if (filialId == null)
|
||||
{
|
||||
status = "Keine Filiale ausgewählt!";
|
||||
return;
|
||||
}
|
||||
|
||||
var alle = Cache.GetArtikel();
|
||||
|
||||
var matches = alle
|
||||
.Where(a =>
|
||||
string.Equals(a.Barcode, input, StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(a.ArtikelNummer, input, StringComparison.OrdinalIgnoreCase))
|
||||
// Aus Datenbank laden!
|
||||
await using var db = await DbFactory.CreateDbContextAsync();
|
||||
var matches = await db.Set<ArtikelFilialPreise>()
|
||||
.Where(a => a.Barcode == input || a.ArtikelNummer == input)
|
||||
.Where(a => a.FilialId == filialId)
|
||||
.OrderBy(a => a.Variante)
|
||||
.ThenBy(a => a.ArtikelVariantenId)
|
||||
.ToList();
|
||||
.ToListAsync();
|
||||
|
||||
if (matches.Count == 0)
|
||||
{
|
||||
status = $"Kein Artikel gefunden für „{input}“.";
|
||||
status = $"Kein Artikel gefunden für „{input}“ in dieser Filiale.";
|
||||
focusBarcode = true;
|
||||
}
|
||||
else if (matches.Count == 1)
|
||||
@ -179,7 +186,7 @@
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private void VarianteAuswaehlen(Artikel art)
|
||||
private void VarianteAuswaehlen(ArtikelFilialPreise art)
|
||||
{
|
||||
gefunden = art;
|
||||
status = $"Variante {GetVarianteLabel(art)} ausgewählt. Bitte Preis eingeben.";
|
||||
@ -187,12 +194,9 @@
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
// ---------- Preis-Masking: "599" -> "5,99" (de-DE) ----------
|
||||
private void OnPriceChanged()
|
||||
{
|
||||
var raw = priceText ?? string.Empty;
|
||||
|
||||
// nur Ziffern nehmen
|
||||
var digits = new string(raw.Where(char.IsDigit).ToArray());
|
||||
|
||||
if (string.IsNullOrEmpty(digits))
|
||||
@ -213,8 +217,6 @@
|
||||
}
|
||||
|
||||
priceCents = cents;
|
||||
|
||||
// Format de-DE mit Komma
|
||||
var de = CultureInfo.GetCultureInfo("de-DE");
|
||||
priceText = (cents / 100m).ToString("N2", de);
|
||||
}
|
||||
@ -248,25 +250,18 @@
|
||||
|
||||
try
|
||||
{
|
||||
// Filiale + User aus localStorage
|
||||
var filialIdStr = await JS.InvokeAsync<string?>("localStorage.getItem", "scannerpilot.filialId");
|
||||
var user = await JS.InvokeAsync<string?>("localStorage.getItem", "scannerpilot.user");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(filialIdStr))
|
||||
throw new InvalidOperationException("Filial-ID fehlt.");
|
||||
var filialIdStr = filialId?.ToString() ?? throw new InvalidOperationException("Filial-ID fehlt.");
|
||||
var user = await JS.InvokeAsync<string>("localStorage.getItem", "scannerpilot.user");
|
||||
if (string.IsNullOrWhiteSpace(user))
|
||||
throw new InvalidOperationException("Benutzername fehlt.");
|
||||
|
||||
filialIdStr = filialIdStr.Trim('"');
|
||||
user = user?.Trim('"');
|
||||
|
||||
var rows = await Preise.SaveAsync(
|
||||
filialIdStr,
|
||||
gefunden.ArtikelId,
|
||||
gefunden.ArtikelVariantenId,
|
||||
gefunden.Variante,
|
||||
priceCents!.Value,
|
||||
user!
|
||||
user.Trim('"')
|
||||
);
|
||||
|
||||
ok = rows > 0;
|
||||
@ -274,7 +269,6 @@
|
||||
? $"Preis gespeichert: {gefunden.ArtikelNummer} – Variante {GetVarianteLabel(gefunden)} · {FormatEuro(priceCents!.Value)}."
|
||||
: "Nichts gespeichert.";
|
||||
|
||||
// Reset für nächste Eingabe
|
||||
priceText = null;
|
||||
priceCents = null;
|
||||
gefunden = null;
|
||||
@ -291,42 +285,13 @@
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
// ---------- Helfer ----------
|
||||
|
||||
private string GetVarianteLabel(Artikel art)
|
||||
private string GetVarianteLabel(ArtikelFilialPreise art)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(art.Text)) return art.Text;
|
||||
if (!string.IsNullOrWhiteSpace(art.Farbe)) return art.Farbe;
|
||||
return $"Variante {art.Variante}";
|
||||
}
|
||||
|
||||
private string GetButtonStyle(Artikel art, bool isActive)
|
||||
{
|
||||
var bg = GetCssColorFromFarbe(art.Farbe);
|
||||
var border = isActive ? "3px solid black" : "1px solid #666";
|
||||
var color = NeedsDarkText(art.Farbe) ? "black" : "white";
|
||||
return $"background-color:{bg};color:{color};border:{border};min-width:6rem;";
|
||||
}
|
||||
|
||||
private string GetCssColorFromFarbe(string? farbe)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(farbe)) return "#e0e0e0";
|
||||
switch (farbe.Trim().ToLowerInvariant())
|
||||
{
|
||||
case "rot": return "#dc3545";
|
||||
case "blau": return "#007bff";
|
||||
case "gruen":
|
||||
case "grün": return "#28a745";
|
||||
case "gelb": return "#ffc107";
|
||||
case "orange": return "#fd7e14";
|
||||
case "lila": return "#6f42c1";
|
||||
default: return "#e0e0e0";
|
||||
}
|
||||
}
|
||||
|
||||
private bool NeedsDarkText(string? farbe)
|
||||
=> string.IsNullOrWhiteSpace(farbe) || farbe.Trim().ToLowerInvariant() is "gelb" or "orange";
|
||||
|
||||
private static string FormatEuro(int cents)
|
||||
=> (cents / 100m).ToString("C", CultureInfo.GetCultureInfo("de-DE"));
|
||||
}
|
||||
|
||||
@ -44,9 +44,9 @@
|
||||
<li class="nav-item">
|
||||
<NavLink class="nav-link text-white" href="/inventur" Match="NavLinkMatch.Prefix">Inventur</NavLink>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
@* <li class="nav-item">
|
||||
<NavLink class="nav-link text-white" href="/stammdaten" Match="NavLinkMatch.Prefix">Stammdaten</NavLink>
|
||||
</li>
|
||||
</li> *@
|
||||
<li class="nav-item">
|
||||
<NavLink class="nav-link text-white" href="/einstellungen" Match="NavLinkMatch.Prefix">Einstellungen</NavLink>
|
||||
</li>
|
||||
@ -76,10 +76,10 @@
|
||||
<NavLink class="nav-link text-white" href="/inventur" Match="NavLinkMatch.Prefix"
|
||||
@onclick="CloseMenu">Inventur</NavLink>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
@* <li class="nav-item">
|
||||
<NavLink class="nav-link text-white" href="/stammdaten" Match="NavLinkMatch.Prefix"
|
||||
@onclick="CloseMenu">Stammdaten</NavLink>
|
||||
</li>
|
||||
</li> *@
|
||||
<li class="nav-item">
|
||||
<NavLink class="nav-link text-white" href="/einstellungen" Match="NavLinkMatch.Prefix"
|
||||
@onclick="CloseMenu">Einstellungen</NavLink>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user