Werksverkauf:

-Preisänderungen ausblenden für Teststellung
This commit is contained in:
Christopher Meinhold 2025-12-03 15:30:36 +01:00
parent c8689880b6
commit 62014e4b63
6 changed files with 77 additions and 84 deletions

View File

@ -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; }
}
}

View File

@ -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!;
}

View File

@ -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 {

View File

@ -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">

View File

@ -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"));
}

View File

@ -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>