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