Compare commits
13 Commits
3414400efe
...
8953e917e1
| Author | SHA1 | Date | |
|---|---|---|---|
| 8953e917e1 | |||
| 6e14130a9c | |||
| f01f05e126 | |||
|
|
c1695b4967 | ||
|
|
205d27c6c7 | ||
|
|
03594bba3f | ||
|
|
2e537337f5 | ||
|
|
9497afe262 | ||
|
|
6df185d519 | ||
|
|
97688542fe | ||
|
|
236eb6792c | ||
|
|
d02414edbb | ||
| be0350f458 |
@@ -20,4 +20,8 @@
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.7" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Pos.ServiceDefaults\Pos.ServiceDefaults.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -4,6 +4,8 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.AddServiceDefaults();
|
||||
|
||||
// Services
|
||||
builder.Services.AddControllers();
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
@@ -13,6 +15,8 @@ builder.Services.AddScoped<EpsonPrintService>();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
app.MapDefaultEndpoints();
|
||||
|
||||
// Swagger (altid aktiv – vi er på en Pi, ikke i produktion)
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
},
|
||||
"EpsonPrinter": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": "true",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "https://localhost:5001;http://localhost:5000",
|
||||
|
||||
@@ -10,3 +10,9 @@ Buket..............1 á 50,50 DKK : 50,50 DKK
|
||||
Total: 50,50 DKK
|
||||
Moms udgør 25% : 12,63 DKK
|
||||
Tak for handlen
|
||||
Dato: 30-07-2026
|
||||
-----------------------------------
|
||||
string : .00
|
||||
-----------------------------------
|
||||
Total salg: .00
|
||||
Antal kunder: 0
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
var builder = DistributedApplication.CreateBuilder(args);
|
||||
|
||||
builder.AddProject<Projects.Database>("database");
|
||||
var epsonPrinter =
|
||||
builder.AddProject<Projects.EpsonPrinter>("epsonprinter");
|
||||
|
||||
builder
|
||||
.AddProject<Projects.Pos_Web>("pos-web")
|
||||
.WithReference(epsonPrinter)
|
||||
.WaitFor(epsonPrinter);
|
||||
|
||||
builder.Build().Run();
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Pos.Ui\Database\Database.csproj" />
|
||||
<ProjectReference Include="..\EpsonPrinterLinux\EpsonPrinter.csproj" />
|
||||
<ProjectReference Include="..\Pos.Web\Pos.Web.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -8,8 +8,8 @@ namespace Pos.Models
|
||||
{
|
||||
public class AmountGridModel
|
||||
{
|
||||
public string PaymentMethodText { get; set; }
|
||||
public string PaymentMethodText { get; set; } = string.Empty;
|
||||
public decimal Amount { get; set; }
|
||||
public string AmountText { get; set; }
|
||||
public string AmountText { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -6,4 +6,4 @@ public enum PaymentMethod
|
||||
Card,
|
||||
GiftCard,
|
||||
MobilePay
|
||||
}
|
||||
}
|
||||
@@ -8,11 +8,11 @@ namespace Pos.Json
|
||||
{
|
||||
public class BodyModel
|
||||
{
|
||||
public ProductModel[] products { get; set; }
|
||||
public ProductModel[] products { get; set; } = Array.Empty<ProductModel>();
|
||||
public float totalPrice { get; set; }
|
||||
public float totalVat { get; set; }
|
||||
public int receiptNumber { get; set; }
|
||||
public string receiptTime { get; set; }
|
||||
public string staff { get; set; }
|
||||
public string receiptTime { get; set; } = string.Empty;
|
||||
public string staff { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,8 @@ namespace Pos.Json
|
||||
{
|
||||
public class FooterModel
|
||||
{
|
||||
public string value { get; set; }
|
||||
public PrintStylesModel printStyles { get; set; }
|
||||
public string value { get; set; } = string.Empty;
|
||||
public PrintStylesModel printStyles { get; set; } = new();
|
||||
public int textAlignment { get; set; }
|
||||
public int feedingLines { get; set; }
|
||||
}
|
||||
@@ -8,8 +8,8 @@ namespace Pos.Json
|
||||
{
|
||||
public class HeaderModel
|
||||
{
|
||||
public string value { get; set; }
|
||||
public PrintStylesModel printStyles { get; set; }
|
||||
public string value { get; set; } = string.Empty;
|
||||
public PrintStylesModel printStyles { get; set; } = new();
|
||||
public int textAlignment { get; set; }
|
||||
public int feedingLines { get; set; }
|
||||
}
|
||||
@@ -8,9 +8,9 @@ namespace Pos.Json
|
||||
{
|
||||
public class PosReceipt
|
||||
{
|
||||
public string logoBase64 { get; set; }
|
||||
public HeaderModel[] header { get; set; }
|
||||
public string logoBase64 { get; set; } = string.Empty;
|
||||
public HeaderModel[] header { get; set; } = Array.Empty<HeaderModel>();
|
||||
public BodyModel bodyModel { get; set; } = new();
|
||||
public FooterModel[] footer { get; set; }
|
||||
public FooterModel[] footer { get; set; } = Array.Empty<FooterModel>();
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,8 @@ namespace Pos.Json
|
||||
{
|
||||
public class ProductModel
|
||||
{
|
||||
public string product { get; set; }
|
||||
public string noOfProduct { get; set; }
|
||||
public string product { get; set; } = string.Empty;
|
||||
public string noOfProduct { get; set; } = string.Empty;
|
||||
public float price { get; set; }
|
||||
public float totalPrice { get; set; }
|
||||
}
|
||||
@@ -13,7 +13,7 @@ namespace Pos.Json
|
||||
|
||||
public class SaleOfDayDetail
|
||||
{
|
||||
public string Category { get; set; }
|
||||
public string Category { get; set; } = string.Empty;
|
||||
public decimal TotalSale { get; set; }
|
||||
}
|
||||
}
|
||||
9
PointOfSale/Pos.Models/Pos.Models.csproj
Normal file
@@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
@@ -8,7 +8,7 @@ namespace Pos.Models
|
||||
{
|
||||
public class PriceLineModel
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public int NumberProducts { get; set; }
|
||||
public decimal Price { get; set; }
|
||||
public int Id { get; set; }
|
||||
11
PointOfSale/Pos.Models/ReceiptDetailModel.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Pos.Models
|
||||
{
|
||||
public class ReceiptDetailModel
|
||||
{
|
||||
public int SaleId { get; set; }
|
||||
public List<ReceiptLineModel> Lines { get; set; } = new();
|
||||
public decimal Total { get; set; }
|
||||
}
|
||||
}
|
||||
10
PointOfSale/Pos.Models/ReceiptLineModel.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Pos.Models
|
||||
{
|
||||
public class ReceiptLineModel
|
||||
{
|
||||
public string Product { get; set; } = string.Empty;
|
||||
public int Pieces { get; set; }
|
||||
public decimal Price { get; set; }
|
||||
public decimal Total { get; set; }
|
||||
}
|
||||
}
|
||||
11
PointOfSale/Pos.Models/ReceiptSummaryModel.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace Pos.Models
|
||||
{
|
||||
public class ReceiptSummaryModel
|
||||
{
|
||||
public int SaleId { get; set; }
|
||||
public DateTime SaleTime { get; set; }
|
||||
public decimal Total { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -8,10 +8,10 @@ namespace Pos.Models
|
||||
{
|
||||
public class SaleGridModel
|
||||
{
|
||||
public string Navn { get; set; }
|
||||
public string Stk { get; set; }
|
||||
public string Pris { get; set; }
|
||||
public string Total { get; set; }
|
||||
public SaleGridInternalModel SaleGridInternalModel { get; set; }
|
||||
public string Navn { get; set; } = string.Empty;
|
||||
public string Stk { get; set; } = string.Empty;
|
||||
public string Pris { get; set; } = string.Empty;
|
||||
public string Total { get; set; } = string.Empty;
|
||||
public SaleGridInternalModel SaleGridInternalModel { get; set; } = new();
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,8 @@ namespace Pos.Models
|
||||
{
|
||||
public class TotalSaleCategory
|
||||
{
|
||||
public string Category { get; set; }
|
||||
public string Category { get; set; } = string.Empty;
|
||||
public decimal Sale { get; set; }
|
||||
public string SaleString { get; set; }
|
||||
public string SaleString { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,8 @@ namespace Pos.Service
|
||||
|
||||
private static bool _invalidEmployee = false;
|
||||
private static bool _invalidProductGroup = false;
|
||||
private static List<EmployeeEntity> _employees;
|
||||
private static List<ProductGroupEntity> _productGroups;
|
||||
private static List<EmployeeEntity> _employees = new();
|
||||
private static List<ProductGroupEntity> _productGroups = new();
|
||||
|
||||
|
||||
public static void Invalidate()
|
||||
67
PointOfSale/Pos.Service/EmployeeService.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Database.Models;
|
||||
using Database.Repository;
|
||||
|
||||
namespace Pos.Service
|
||||
{
|
||||
public class EmployeeService
|
||||
{
|
||||
private readonly EmployeeRepository _employeeRepository =
|
||||
new EmployeeRepository();
|
||||
|
||||
public List<EmployeeEntity> GetEmployees()
|
||||
{
|
||||
return _employeeRepository.GetAll();
|
||||
}
|
||||
|
||||
public void AddEmployee(string name)
|
||||
{
|
||||
_employeeRepository.Add(ValidateName(name));
|
||||
CacheService.Invalidate();
|
||||
}
|
||||
|
||||
public void RenameEmployee(int employeeId, string name)
|
||||
{
|
||||
ValidateId(employeeId);
|
||||
_employeeRepository.Edit(employeeId, ValidateName(name));
|
||||
CacheService.Invalidate();
|
||||
}
|
||||
|
||||
public void ArchiveEmployee(int employeeId)
|
||||
{
|
||||
ValidateId(employeeId);
|
||||
_employeeRepository.Delete(employeeId);
|
||||
CacheService.Invalidate();
|
||||
}
|
||||
|
||||
private static string ValidateName(string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
throw new ArgumentException(
|
||||
"Medarbejderens navn skal være udfyldt.",
|
||||
nameof(name));
|
||||
|
||||
return name.Trim();
|
||||
}
|
||||
|
||||
private static void ValidateId(int employeeId)
|
||||
{
|
||||
if (employeeId <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(employeeId));
|
||||
}
|
||||
|
||||
public async Task<List<EmployeeEntity>> GetEmployeesAsync()
|
||||
{
|
||||
return await _employeeRepository.GetAllAsync();
|
||||
}
|
||||
|
||||
public async Task<EmployeeEntity> AddEmployeeAsync(string name)
|
||||
{
|
||||
EmployeeEntity employee =
|
||||
await _employeeRepository.AddAsync(ValidateName(name));
|
||||
CacheService.Invalidate();
|
||||
return employee;
|
||||
}
|
||||
}
|
||||
}
|
||||
19
PointOfSale/Pos.Service/Pos.Service.csproj
Normal file
@@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.10" />
|
||||
<PackageReference Include="RestSharp" Version="108.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Pos.Models\Pos.Models.csproj" />
|
||||
<ProjectReference Include="..\Pos.Ui\Database\Database.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
85
PointOfSale/Pos.Service/ProductGroupService.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Database.Models;
|
||||
using Database.Repository;
|
||||
|
||||
namespace Pos.Service
|
||||
{
|
||||
public class ProductGroupService
|
||||
{
|
||||
private readonly ProductGroupRepository _productGroupRepository =
|
||||
new ProductGroupRepository();
|
||||
|
||||
public List<ProductGroupEntity> GetProductGroups()
|
||||
{
|
||||
return _productGroupRepository.GetAll();
|
||||
}
|
||||
|
||||
public bool HasProductGroups()
|
||||
{
|
||||
return _productGroupRepository.Any();
|
||||
}
|
||||
|
||||
public void AddProductGroup(string name)
|
||||
{
|
||||
_productGroupRepository.Add(ValidateName(name));
|
||||
CacheService.Invalidate();
|
||||
}
|
||||
|
||||
public void RenameProductGroup(int productGroupId, string name)
|
||||
{
|
||||
ValidateId(productGroupId);
|
||||
_productGroupRepository.Edit(
|
||||
ValidateName(name),
|
||||
productGroupId);
|
||||
CacheService.Invalidate();
|
||||
}
|
||||
|
||||
public void ArchiveProductGroup(int productGroupId)
|
||||
{
|
||||
ValidateId(productGroupId);
|
||||
_productGroupRepository.Archive(productGroupId);
|
||||
CacheService.Invalidate();
|
||||
}
|
||||
|
||||
public void UpdateOrder(IReadOnlyList<int> orderedProductGroupIds)
|
||||
{
|
||||
ValidateOrder(orderedProductGroupIds);
|
||||
_productGroupRepository.SetOrder(orderedProductGroupIds);
|
||||
CacheService.Invalidate();
|
||||
}
|
||||
|
||||
private static string ValidateName(string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
throw new ArgumentException(
|
||||
"Produktgruppen skal have et navn.",
|
||||
nameof(name));
|
||||
|
||||
return name.Trim();
|
||||
}
|
||||
|
||||
private static void ValidateId(int productGroupId)
|
||||
{
|
||||
if (productGroupId <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(productGroupId));
|
||||
}
|
||||
|
||||
private static void ValidateOrder(IReadOnlyList<int> orderedIds)
|
||||
{
|
||||
if (orderedIds == null)
|
||||
throw new ArgumentNullException(nameof(orderedIds));
|
||||
|
||||
if (orderedIds.Any(id => id <= 0))
|
||||
throw new ArgumentException(
|
||||
"Rækkefølgen indeholder et ugyldigt id.",
|
||||
nameof(orderedIds));
|
||||
|
||||
if (orderedIds.Distinct().Count() != orderedIds.Count)
|
||||
throw new ArgumentException(
|
||||
"Rækkefølgen indeholder dubletter.",
|
||||
nameof(orderedIds));
|
||||
}
|
||||
}
|
||||
}
|
||||
95
PointOfSale/Pos.Service/ProductService.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Database.Models;
|
||||
using Database.Repository;
|
||||
|
||||
namespace Pos.Service
|
||||
{
|
||||
public class ProductService
|
||||
{
|
||||
private readonly ProductRepository _productRepository =
|
||||
new ProductRepository();
|
||||
|
||||
public List<ProductEntity> GetProducts(int productGroupId)
|
||||
{
|
||||
ValidateId(productGroupId, nameof(productGroupId));
|
||||
return _productRepository.GetByProductGroup(productGroupId);
|
||||
}
|
||||
|
||||
public ProductEntity GetProduct(int productId)
|
||||
{
|
||||
ValidateId(productId, nameof(productId));
|
||||
return _productRepository.GetById(productId);
|
||||
}
|
||||
|
||||
public void AddProduct(string name, int productGroupId)
|
||||
{
|
||||
ValidateId(productGroupId, nameof(productGroupId));
|
||||
_productRepository.Add(ValidateName(name), productGroupId);
|
||||
CacheService.Invalidate();
|
||||
}
|
||||
|
||||
public void UpdateProduct(
|
||||
int productId,
|
||||
int productGroupId,
|
||||
string name)
|
||||
{
|
||||
ValidateId(productId, nameof(productId));
|
||||
ValidateId(productGroupId, nameof(productGroupId));
|
||||
_productRepository.Update(
|
||||
productId,
|
||||
productGroupId,
|
||||
ValidateName(name));
|
||||
CacheService.Invalidate();
|
||||
}
|
||||
|
||||
public void ArchiveProduct(int productId)
|
||||
{
|
||||
ValidateId(productId, nameof(productId));
|
||||
_productRepository.Archive(productId);
|
||||
CacheService.Invalidate();
|
||||
}
|
||||
|
||||
public void UpdateOrder(
|
||||
int productGroupId,
|
||||
IReadOnlyList<int> orderedProductIds)
|
||||
{
|
||||
ValidateId(productGroupId, nameof(productGroupId));
|
||||
|
||||
if (orderedProductIds == null)
|
||||
throw new ArgumentNullException(nameof(orderedProductIds));
|
||||
|
||||
if (orderedProductIds.Any(id => id <= 0))
|
||||
throw new ArgumentException(
|
||||
"Rækkefølgen indeholder et ugyldigt id.",
|
||||
nameof(orderedProductIds));
|
||||
|
||||
if (orderedProductIds.Distinct().Count() != orderedProductIds.Count)
|
||||
throw new ArgumentException(
|
||||
"Rækkefølgen indeholder dubletter.",
|
||||
nameof(orderedProductIds));
|
||||
|
||||
_productRepository.SetOrder(
|
||||
productGroupId,
|
||||
orderedProductIds);
|
||||
CacheService.Invalidate();
|
||||
}
|
||||
|
||||
private static string ValidateName(string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
throw new ArgumentException(
|
||||
"Produktet skal have et navn.",
|
||||
nameof(name));
|
||||
|
||||
return name.Trim();
|
||||
}
|
||||
|
||||
private static void ValidateId(int id, string parameterName)
|
||||
{
|
||||
if (id <= 0)
|
||||
throw new ArgumentOutOfRangeException(parameterName);
|
||||
}
|
||||
}
|
||||
}
|
||||
74
PointOfSale/Pos.Service/SaleHistoryService.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Database.Models;
|
||||
using Database.Repository;
|
||||
using Pos.Models;
|
||||
|
||||
namespace Pos.Service
|
||||
{
|
||||
public class SaleHistoryService
|
||||
{
|
||||
private readonly SaleRepository _saleRepository =
|
||||
new SaleRepository();
|
||||
|
||||
public List<ReceiptSummaryModel> Search(
|
||||
DateTime startDate,
|
||||
DateTime endDate)
|
||||
{
|
||||
if (endDate.Date < startDate.Date)
|
||||
throw new ArgumentException(
|
||||
"Slutdatoen må ikke ligge før startdatoen.");
|
||||
|
||||
List<SaleEntity> sales =
|
||||
_saleRepository.GetByDateRange(startDate, endDate);
|
||||
|
||||
List<ReceiptSummaryModel> receipts =
|
||||
new List<ReceiptSummaryModel>();
|
||||
|
||||
foreach (SaleEntity sale in sales)
|
||||
{
|
||||
decimal total = _saleRepository
|
||||
.GetSaleLineBySaleId(sale.Id)
|
||||
.Sum(line => line.Total);
|
||||
|
||||
receipts.Add(new ReceiptSummaryModel
|
||||
{
|
||||
SaleId = sale.Id,
|
||||
SaleTime = sale.Time,
|
||||
Total = total
|
||||
});
|
||||
}
|
||||
|
||||
return receipts;
|
||||
}
|
||||
|
||||
public ReceiptDetailModel GetReceipt(int saleId)
|
||||
{
|
||||
if (saleId <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(saleId));
|
||||
|
||||
List<SaleLineEntity> saleLines =
|
||||
_saleRepository.GetSaleLineBySaleId(saleId);
|
||||
|
||||
ReceiptDetailModel receipt = new ReceiptDetailModel
|
||||
{
|
||||
SaleId = saleId
|
||||
};
|
||||
|
||||
foreach (SaleLineEntity saleLine in saleLines)
|
||||
{
|
||||
receipt.Lines.Add(new ReceiptLineModel
|
||||
{
|
||||
Product = saleLine.Product,
|
||||
Pieces = saleLine.Pieces,
|
||||
Price = saleLine.Price,
|
||||
Total = saleLine.Total
|
||||
});
|
||||
}
|
||||
|
||||
receipt.Total = receipt.Lines.Sum(line => line.Total);
|
||||
return receipt;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,7 +84,9 @@ namespace Pos.Service
|
||||
|
||||
LoadConfig loadConfig = new LoadConfig();
|
||||
IConfiguration config = loadConfig.ByEnvironment();
|
||||
string url = config["API:URL"];
|
||||
string url = config["API:URL"]
|
||||
?? throw new InvalidOperationException(
|
||||
"API:URL mangler i konfigurationen.");
|
||||
|
||||
RestClient restClient = new RestClient($"{url}/api/PosPrinter/SaleOfDay");
|
||||
RestRequest request = new RestRequest();
|
||||
@@ -159,7 +161,9 @@ namespace Pos.Service
|
||||
|
||||
LoadConfig loadConfig = new LoadConfig();
|
||||
IConfiguration config = loadConfig.ByEnvironment();
|
||||
string apiUrl = config["API:URL"];
|
||||
string apiUrl = config["API:URL"]
|
||||
?? throw new InvalidOperationException(
|
||||
"API:URL mangler i konfigurationen.");
|
||||
string url = $"{apiUrl}/api/PosPrinter/Receipt";
|
||||
|
||||
RestClient restClient = new RestClient(url);
|
||||
171
PointOfSale/Pos.Service/SalesTransactionService.cs
Normal file
@@ -0,0 +1,171 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Pos.Models;
|
||||
|
||||
namespace Pos.Service
|
||||
{
|
||||
/// <summary>
|
||||
/// Holder styr på den igangværende handel og dens beregninger.
|
||||
/// Servicen har ingen afhængighed til WPF eller web.
|
||||
/// </summary>
|
||||
public class SalesTransactionService
|
||||
{
|
||||
private readonly List<PriceLineModel> _saleLines = new List<PriceLineModel>();
|
||||
private readonly List<AmountGridModel> _payments = new List<AmountGridModel>();
|
||||
|
||||
public IReadOnlyList<PriceLineModel> SaleLines => _saleLines;
|
||||
public IReadOnlyList<AmountGridModel> Payments => _payments;
|
||||
|
||||
public decimal TotalPrice =>
|
||||
_saleLines.Sum(line =>
|
||||
CalculateLineTotal(line.Price, line.NumberProducts));
|
||||
|
||||
public decimal AmountPaid =>
|
||||
_payments.Sum(payment => payment.Amount);
|
||||
|
||||
public decimal RemainingAmount =>
|
||||
Math.Max(TotalPrice - AmountPaid, 0);
|
||||
|
||||
public decimal Change =>
|
||||
Math.Max(AmountPaid - TotalPrice, 0);
|
||||
|
||||
public bool IsFullyPaid =>
|
||||
TotalPrice > 0 && AmountPaid >= TotalPrice;
|
||||
|
||||
/// <summary>
|
||||
/// Beregner totalen for en enkelt salgslinje.
|
||||
/// Parsing og visning af resultatet er fortsat UI-ansvar.
|
||||
/// </summary>
|
||||
public decimal CalculateLineTotal(decimal price, int numberOfProducts)
|
||||
{
|
||||
ValidatePriceAndQuantity(price, numberOfProducts);
|
||||
return price * numberOfProducts;
|
||||
}
|
||||
|
||||
public PriceLineModel AddSaleLine(
|
||||
int id,
|
||||
string name,
|
||||
int numberOfProducts,
|
||||
decimal price,
|
||||
bool isProductGroup)
|
||||
{
|
||||
if (id <= 0)
|
||||
throw new ArgumentOutOfRangeException(
|
||||
nameof(id),
|
||||
"Id skal være større end 0.");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
throw new ArgumentException(
|
||||
"Navn skal være udfyldt.",
|
||||
nameof(name));
|
||||
|
||||
ValidatePriceAndQuantity(price, numberOfProducts);
|
||||
|
||||
PriceLineModel saleLine = new PriceLineModel
|
||||
{
|
||||
Id = id,
|
||||
Name = name,
|
||||
NumberProducts = numberOfProducts,
|
||||
Price = price,
|
||||
IsProductGroup = isProductGroup
|
||||
};
|
||||
|
||||
_saleLines.Add(saleLine);
|
||||
return saleLine;
|
||||
}
|
||||
|
||||
public void RemoveSaleLineAt(int index)
|
||||
{
|
||||
if (index < 0 || index >= _saleLines.Count)
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
|
||||
_saleLines.RemoveAt(index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registrerer en betaling. Hvis amount er null, betales restbeløbet.
|
||||
/// </summary>
|
||||
public AmountGridModel AddPayment(
|
||||
PaymentMethod paymentMethod,
|
||||
decimal? amount = null)
|
||||
{
|
||||
if (_saleLines.Count == 0)
|
||||
throw new InvalidOperationException(
|
||||
"Der kan ikke registreres betaling på et tomt salg.");
|
||||
|
||||
decimal paymentAmount = amount ?? RemainingAmount;
|
||||
|
||||
if (paymentAmount <= 0)
|
||||
throw new ArgumentOutOfRangeException(
|
||||
nameof(amount),
|
||||
"Betalingsbeløbet skal være større end 0.");
|
||||
|
||||
AmountGridModel payment = new AmountGridModel
|
||||
{
|
||||
Amount = paymentAmount,
|
||||
PaymentMethodText = GetPaymentMethodText(paymentMethod)
|
||||
};
|
||||
|
||||
_payments.Add(payment);
|
||||
return payment;
|
||||
}
|
||||
|
||||
public void RemovePaymentAt(int index)
|
||||
{
|
||||
if (index < 0 || index >= _payments.Count)
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
|
||||
_payments.RemoveAt(index);
|
||||
}
|
||||
|
||||
public void EnsureCanComplete()
|
||||
{
|
||||
if (_saleLines.Count == 0)
|
||||
throw new InvalidOperationException(
|
||||
"Et tomt salg kan ikke afsluttes.");
|
||||
|
||||
if (!IsFullyPaid)
|
||||
throw new InvalidOperationException(
|
||||
"Salget er ikke fuldt betalt.");
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_saleLines.Clear();
|
||||
_payments.Clear();
|
||||
}
|
||||
|
||||
private static void ValidatePriceAndQuantity(
|
||||
decimal price,
|
||||
int numberOfProducts)
|
||||
{
|
||||
if (price < 0)
|
||||
throw new ArgumentOutOfRangeException(
|
||||
nameof(price),
|
||||
"Prisen må ikke være negativ.");
|
||||
|
||||
if (numberOfProducts <= 0)
|
||||
throw new ArgumentOutOfRangeException(
|
||||
nameof(numberOfProducts),
|
||||
"Antal skal være større end 0.");
|
||||
}
|
||||
|
||||
private static string GetPaymentMethodText(PaymentMethod paymentMethod)
|
||||
{
|
||||
switch (paymentMethod)
|
||||
{
|
||||
case PaymentMethod.Card:
|
||||
return "Kort";
|
||||
case PaymentMethod.Cash:
|
||||
return "Kontant";
|
||||
case PaymentMethod.GiftCard:
|
||||
return "Gavekort";
|
||||
case PaymentMethod.MobilePay:
|
||||
return "MobilePay";
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(paymentMethod));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsAspireSharedProject>true</IsAspireSharedProject>
|
||||
|
||||
@@ -17,6 +17,16 @@ namespace Database
|
||||
public DbSet<SaleLineEntity> SalesLines { get; set; }
|
||||
public DbSet<PaymentEntity> Payment { get; set; }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<SaleLineEntity>()
|
||||
.Property(saleLine => saleLine.Pieces)
|
||||
.HasConversion<short>()
|
||||
.HasColumnType("smallint");
|
||||
|
||||
base.OnModelCreating(modelBuilder);
|
||||
}
|
||||
|
||||
protected override void OnConfiguring(
|
||||
DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
using System;
|
||||
using Database.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Database.Models;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Database.Repository
|
||||
{
|
||||
@@ -58,5 +59,32 @@ namespace Database.Repository
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
public async Task<List<EmployeeEntity>> GetAllAsync()
|
||||
{
|
||||
await using PosDbContext context = new PosDbContext();
|
||||
|
||||
return await context.Employee
|
||||
.AsNoTracking()
|
||||
.Where(employee => !employee.IsArchived)
|
||||
.OrderBy(employee => employee.Name)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<EmployeeEntity> AddAsync(string name)
|
||||
{
|
||||
EmployeeEntity employee = new EmployeeEntity
|
||||
{
|
||||
Name = name,
|
||||
IsModified = true
|
||||
};
|
||||
|
||||
await using PosDbContext context = new PosDbContext();
|
||||
|
||||
context.Employee.Add(employee);
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
return employee;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,10 +69,30 @@ namespace Database.Repository
|
||||
context.SaveChanges();
|
||||
}
|
||||
|
||||
public void SetOrder(IReadOnlyList<int> orderedIds)
|
||||
{
|
||||
using PosDbContext context = new PosDbContext();
|
||||
List<ProductGroupEntity> productGroups = context.ProductGroups
|
||||
.Where(productGroup => orderedIds.Contains(productGroup.Id))
|
||||
.ToList();
|
||||
|
||||
if (productGroups.Count != orderedIds.Count)
|
||||
throw new InvalidOperationException(
|
||||
"En eller flere produktgrupper findes ikke.");
|
||||
|
||||
Dictionary<int, ProductGroupEntity> productGroupsById =
|
||||
productGroups.ToDictionary(productGroup => productGroup.Id);
|
||||
|
||||
for (int index = 0; index < orderedIds.Count; index++)
|
||||
productGroupsById[orderedIds[index]].Index = index;
|
||||
|
||||
context.SaveChanges();
|
||||
}
|
||||
|
||||
public bool Any()
|
||||
{
|
||||
using PosDbContext context = new PosDbContext();
|
||||
bool any = context.ProductGroups.Any();
|
||||
bool any = context.ProductGroups.Any(c => !c.IsArchived);
|
||||
return any;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Database.Models;
|
||||
|
||||
@@ -38,6 +39,33 @@ namespace Database.Repository
|
||||
context.SaveChanges();
|
||||
}
|
||||
|
||||
public void SetOrder(
|
||||
int productGroupId,
|
||||
IReadOnlyList<int> orderedIds)
|
||||
{
|
||||
using PosDbContext context = new PosDbContext();
|
||||
List<ProductEntity> products = context.Products
|
||||
.Where(product => product.ProductGroupId == productGroupId)
|
||||
.Where(product => orderedIds.Contains(product.Id))
|
||||
.ToList();
|
||||
|
||||
if (products.Count != orderedIds.Count)
|
||||
throw new InvalidOperationException(
|
||||
"Et eller flere produkter findes ikke i produktgruppen.");
|
||||
|
||||
Dictionary<int, ProductEntity> productsById =
|
||||
products.ToDictionary(product => product.Id);
|
||||
|
||||
for (int index = 0; index < orderedIds.Count; index++)
|
||||
{
|
||||
ProductEntity product = productsById[orderedIds[index]];
|
||||
product.Index = index;
|
||||
product.IsModified = true;
|
||||
}
|
||||
|
||||
context.SaveChanges();
|
||||
}
|
||||
|
||||
public ProductEntity GetById(int id)
|
||||
{
|
||||
using PosDbContext context = new PosDbContext();
|
||||
@@ -72,7 +100,11 @@ namespace Database.Repository
|
||||
product.ProductGroupId = productGroupId;
|
||||
using PosDbContext context = new PosDbContext();
|
||||
//Get the highest index
|
||||
ProductEntity highest = context.Products.OrderByDescending(c => c.Index).Take(1).FirstOrDefault();
|
||||
ProductEntity highest = context.Products
|
||||
.Where(c => c.ProductGroupId == productGroupId)
|
||||
.OrderByDescending(c => c.Index)
|
||||
.Take(1)
|
||||
.FirstOrDefault();
|
||||
if (highest == null)
|
||||
{
|
||||
highest = new ProductEntity();
|
||||
|
||||
@@ -18,12 +18,10 @@
|
||||
<None Remove="Icons\settings.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RestSharp" Version="108.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Database\Database.csproj" />
|
||||
<ProjectReference Include="..\..\Pos.Models\Pos.Models.csproj" />
|
||||
<ProjectReference Include="..\..\Pos.Service\Pos.Service.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -11,8 +11,7 @@ using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
using Database.Models;
|
||||
using Database.Repository;
|
||||
using Pos.Models;
|
||||
using Pos.Service;
|
||||
|
||||
namespace Pos
|
||||
@@ -25,6 +24,9 @@ namespace Pos
|
||||
private List<ListReceiptModel> _receiptList = new List<ListReceiptModel>();
|
||||
private bool _init = false;
|
||||
private int _saleId = 0;
|
||||
private readonly SaleHistoryService _saleHistoryService =
|
||||
new SaleHistoryService();
|
||||
private readonly SaleService _saleService = new SaleService();
|
||||
|
||||
public PrintReceiptWindow()
|
||||
{
|
||||
@@ -38,28 +40,39 @@ namespace Pos
|
||||
|
||||
private void Search_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DateTime startDate = (DateTime)StartDatePicker.SelectedDate;
|
||||
DateTime endDate = (DateTime)EndDatePicker.SelectedDate;
|
||||
SaleRepository saleRepository = new SaleRepository();
|
||||
List<SaleEntity> saleEntities = saleRepository.GetByDateRange(startDate, endDate);
|
||||
foreach (SaleEntity entity in saleEntities)
|
||||
{
|
||||
List<SaleLineEntity> saleLineBySaleId = saleRepository.GetSaleLineBySaleId(entity.Id);
|
||||
decimal total = 0;
|
||||
foreach (SaleLineEntity lineEntity in saleLineBySaleId)
|
||||
{
|
||||
total += lineEntity.Total;
|
||||
}
|
||||
if (StartDatePicker.SelectedDate is not DateTime startDate ||
|
||||
EndDatePicker.SelectedDate is not DateTime endDate)
|
||||
return;
|
||||
|
||||
List<ReceiptSummaryModel> receipts;
|
||||
try
|
||||
{
|
||||
receipts = _saleHistoryService.Search(startDate, endDate);
|
||||
}
|
||||
catch (ArgumentException exception)
|
||||
{
|
||||
MessageBox.Show(
|
||||
exception.Message,
|
||||
"Ugyldigt datointerval",
|
||||
MessageBoxButton.OK,
|
||||
MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
_receiptList.Clear();
|
||||
foreach (ReceiptSummaryModel receipt in receipts)
|
||||
{
|
||||
ListReceiptModel receiptModel = new ListReceiptModel
|
||||
{
|
||||
Id = entity.Id,
|
||||
Content = $"{entity.Time:dd-MM-yyyy HH:mm} - Pris: {total:0.00}"
|
||||
Id = receipt.SaleId,
|
||||
Content =
|
||||
$"{receipt.SaleTime:dd-MM-yyyy HH:mm} - Pris: {receipt.Total:0.00}"
|
||||
};
|
||||
_receiptList.Add(receiptModel);
|
||||
}
|
||||
|
||||
_init = true;
|
||||
ListReciept.ItemsSource = null;
|
||||
ListReciept.ItemsSource = _receiptList;
|
||||
_init = false;
|
||||
}
|
||||
@@ -78,23 +91,21 @@ namespace Pos
|
||||
|
||||
private void ListReciept_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (_init)
|
||||
if (_init || e.AddedItems.Count == 0)
|
||||
return;
|
||||
|
||||
ListReceiptModel data = (ListReceiptModel) e.AddedItems[0];
|
||||
_saleId = data.Id;
|
||||
SaleRepository saleRepository = new SaleRepository();
|
||||
List<SaleLineEntity> saleLineBySaleId = saleRepository.GetSaleLineBySaleId(data.Id);
|
||||
ReceiptDetailModel receipt =
|
||||
_saleHistoryService.GetReceipt(data.Id);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
decimal total = 0;
|
||||
foreach (SaleLineEntity saleLine in saleLineBySaleId)
|
||||
foreach (ReceiptLineModel saleLine in receipt.Lines)
|
||||
{
|
||||
total += saleLine.Total;
|
||||
sb.AppendLine(
|
||||
$"{saleLine.Product} - {saleLine.Pieces} stk. a' {saleLine.Price.ToString("0.00")} = {saleLine.Total.ToString("0.00")}");
|
||||
$"{saleLine.Product} - {saleLine.Pieces} stk. a' {saleLine.Price:0.00} = {saleLine.Total:0.00}");
|
||||
}
|
||||
|
||||
sb.AppendLine($"Total: {total.ToString("0.00")} kr.");
|
||||
sb.AppendLine($"Total: {receipt.Total:0.00} kr.");
|
||||
PrintReceipt.Visibility = Visibility.Visible;
|
||||
SaleLines.Clear();
|
||||
SaleLines.Text = sb.ToString();
|
||||
@@ -102,8 +113,7 @@ namespace Pos
|
||||
|
||||
private void PrintReceipt_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SaleService saleService = new SaleService();
|
||||
saleService.PrintReceipt(_saleId);
|
||||
_saleService.PrintReceipt(_saleId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,11 +13,11 @@ namespace Pos
|
||||
{
|
||||
private bool _initialLoad = true;
|
||||
private TotalSaleDetail totalSaleDetail;
|
||||
private readonly SaleService _saleService = new SaleService();
|
||||
public SaleOfDay()
|
||||
{
|
||||
InitializeComponent();
|
||||
SaleService saleService = new SaleService();
|
||||
totalSaleDetail = saleService.TotalSale(DateTime.Now);
|
||||
totalSaleDetail = _saleService.TotalSale(DateTime.Now);
|
||||
TotalSale.Content = $"Dagens salg: kr. {totalSaleDetail.TotalSale:0.00}";
|
||||
StartDatePicker.SelectedDate = DateTime.Now;
|
||||
GridSaleDetail.ItemsSource = totalSaleDetail.TotalSaleCategories;
|
||||
@@ -29,18 +29,20 @@ namespace Pos
|
||||
if (_initialLoad)
|
||||
return;
|
||||
|
||||
_initialLoad = false;
|
||||
DateTime selectedDate = DateTime.Parse(sender.ToString());
|
||||
SaleService saleService = new SaleService();
|
||||
totalSaleDetail = saleService.TotalSale(selectedDate);
|
||||
if (StartDatePicker.SelectedDate is not DateTime selectedDate)
|
||||
return;
|
||||
|
||||
totalSaleDetail = _saleService.TotalSale(selectedDate);
|
||||
TotalSale.Content = $"Dagens salg: kr. {totalSaleDetail.TotalSale:0.00}";
|
||||
GridSaleDetail.ItemsSource = totalSaleDetail.TotalSaleCategories;
|
||||
}
|
||||
|
||||
private void PrintSale_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SaleService saleService = new SaleService();
|
||||
saleService.PrintSaleOfDay(StartDatePicker.SelectedDate.Value,totalSaleDetail);
|
||||
if (StartDatePicker.SelectedDate is not DateTime selectedDate)
|
||||
return;
|
||||
|
||||
_saleService.PrintSaleOfDay(selectedDate, totalSaleDetail);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,8 @@ namespace Pos
|
||||
private ObservableCollection<ProductGroupEntity> _productGroups;
|
||||
private ObservableCollection<SaleGridModel> _saleGridModels;
|
||||
private ObservableCollection<AmountGridModel> _paymentGridModels;
|
||||
private List<PriceLineModel> _priceLineModels;
|
||||
private readonly SalesTransactionService _salesTransactionService =
|
||||
new SalesTransactionService();
|
||||
|
||||
private ProductEntity _selectedProduct;
|
||||
private ProductGroupEntity _selectedProductGroup;
|
||||
@@ -37,14 +38,10 @@ namespace Pos
|
||||
private int _employeeId = -1;
|
||||
private int _productGroupId = -1;
|
||||
private int _productId = -1;
|
||||
private int _numberOfProducts = 1;
|
||||
private int _selectedSaleGridIndex = -1;
|
||||
|
||||
private int _maxCharOfName = 0;
|
||||
|
||||
private decimal _price = 0;
|
||||
private decimal _totalPrice = 0;
|
||||
|
||||
private Button _prevEmpButton = new Button();
|
||||
private Button _prevProductGroupButton = new Button();
|
||||
private Button _prevProduct = new Button();
|
||||
@@ -61,7 +58,6 @@ namespace Pos
|
||||
_employees = CacheService.GetEmployee() as ObservableCollection<EmployeeEntity>;
|
||||
_productGroups = CacheService.GetProductGroupsIncludeProducts() as ObservableCollection<ProductGroupEntity>;
|
||||
_saleGridModels = new ObservableCollection<SaleGridModel>();
|
||||
_priceLineModels = new List<PriceLineModel>();
|
||||
_paymentGridModels = new ObservableCollection<AmountGridModel>();
|
||||
SaleGrid.ItemsSource = _saleGridModels;
|
||||
PaymentGrid.ItemsSource = _paymentGridModels;
|
||||
@@ -150,11 +146,25 @@ namespace Pos
|
||||
return;
|
||||
|
||||
decimal price = Convert.ToDecimal(Price.Text);
|
||||
int number = Convert.ToInt32(NumberOfProducts.Text);
|
||||
int numberOfProducts = Convert.ToInt32(NumberOfProducts.Text);
|
||||
|
||||
try
|
||||
{
|
||||
BuildPrice(numberOfProducts, price);
|
||||
}
|
||||
catch (ArgumentException exception)
|
||||
{
|
||||
MessageBox.Show(
|
||||
exception.Message,
|
||||
"Ugyldig salgslinje",
|
||||
MessageBoxButton.OK,
|
||||
MessageBoxImage.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
NumberOfProducts.Text = "1";
|
||||
Price.Text = string.Empty;
|
||||
TotalPrice.Content = "0";
|
||||
BuildPrice(false);
|
||||
_productId = -1;
|
||||
_productGroupId = -1;
|
||||
ManipulateBuySection();
|
||||
@@ -162,16 +172,36 @@ namespace Pos
|
||||
|
||||
private void Payment_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
//bool isValid = ValidateAmountInput();
|
||||
//if (!isValid)
|
||||
// return;
|
||||
decimal amount = -1;
|
||||
decimal? amount = null;
|
||||
if (!string.IsNullOrEmpty(Amount.Text))
|
||||
{
|
||||
bool isValid = ValidateAmountInput();
|
||||
if (!isValid)
|
||||
return;
|
||||
|
||||
amount = Convert.ToDecimal(Amount.Text);
|
||||
}
|
||||
|
||||
Amount.Text = string.Empty;
|
||||
Button button = sender as Button;
|
||||
BuildAmuntDataGrid(amount,(string)button.Tag);
|
||||
string paymentMethodName =
|
||||
((string)button.Tag).Replace("PaymentMethod.", string.Empty);
|
||||
|
||||
if (!Enum.TryParse(paymentMethodName, out PaymentMethod paymentMethod))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
BuildAmuntDataGrid(amount, paymentMethod);
|
||||
}
|
||||
catch (ArgumentException exception)
|
||||
{
|
||||
MessageBox.Show(
|
||||
exception.Message,
|
||||
"Ugyldig betaling",
|
||||
MessageBoxButton.OK,
|
||||
MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void NumberOfProducts_LostFocus(object sender, RoutedEventArgs e)
|
||||
@@ -249,61 +279,69 @@ namespace Pos
|
||||
}
|
||||
|
||||
}
|
||||
private void BuildPrice(bool returnProduct)
|
||||
private void BuildPrice(int numberOfProducts, decimal price)
|
||||
{
|
||||
PriceLineModel pModel = new PriceLineModel();
|
||||
pModel.NumberProducts = _numberOfProducts;
|
||||
pModel.Price = _price;
|
||||
PriceLineModel priceLineModel;
|
||||
if (_selectedProduct != null)
|
||||
{
|
||||
pModel.Id = _selectedProduct.Id;
|
||||
pModel.Name = _selectedProduct.Name;
|
||||
pModel.IsProductGroup = false;
|
||||
priceLineModel = _salesTransactionService.AddSaleLine(
|
||||
_selectedProduct.Id,
|
||||
_selectedProduct.Name,
|
||||
numberOfProducts,
|
||||
price,
|
||||
false);
|
||||
}
|
||||
else
|
||||
{
|
||||
pModel.Id = _selectedProductGroup.Id;
|
||||
pModel.Name = _selectedProductGroup.Name;
|
||||
pModel.IsProductGroup = true;
|
||||
priceLineModel = _salesTransactionService.AddSaleLine(
|
||||
_selectedProductGroup.Id,
|
||||
_selectedProductGroup.Name,
|
||||
numberOfProducts,
|
||||
price,
|
||||
true);
|
||||
}
|
||||
|
||||
BuildPriceDataGrid(pModel);
|
||||
BuildPriceDataGrid(priceLineModel);
|
||||
//BuildEmployee();
|
||||
BuildProductGroup();
|
||||
}
|
||||
|
||||
private void CalculateSubPrice()
|
||||
{
|
||||
_price = 0;
|
||||
bool isValid = decimal.TryParse(Price.Text, out _price);
|
||||
bool isValid = decimal.TryParse(Price.Text, out decimal price);
|
||||
if (!isValid)
|
||||
return;
|
||||
|
||||
isValid = Int32.TryParse(NumberOfProducts.Text, out _numberOfProducts);
|
||||
isValid = Int32.TryParse(
|
||||
NumberOfProducts.Text,
|
||||
out int numberOfProducts);
|
||||
if (!isValid)
|
||||
return;
|
||||
|
||||
TotalPrice.Content = _price * _numberOfProducts;
|
||||
try
|
||||
{
|
||||
TotalPrice.Content =
|
||||
_salesTransactionService.CalculateLineTotal(
|
||||
price,
|
||||
numberOfProducts);
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
{
|
||||
TotalPrice.Content = "0";
|
||||
}
|
||||
}
|
||||
|
||||
private void CalculateTotalLine()
|
||||
{
|
||||
_totalPrice = 0;
|
||||
foreach (PriceLineModel model in _priceLineModels)
|
||||
{
|
||||
_totalPrice += model.Price * model.NumberProducts;
|
||||
}
|
||||
|
||||
Total.Content = $"At betale: Kr. {_totalPrice}";
|
||||
Total.Content =
|
||||
$"At betale: Kr. {_salesTransactionService.TotalPrice}";
|
||||
}
|
||||
|
||||
private void BuildPriceDataGrid(PriceLineModel priceLineModel)
|
||||
{
|
||||
_selectedProduct = null;
|
||||
_selectedProductGroup = null;
|
||||
_numberOfProducts = 1;
|
||||
|
||||
_priceLineModels.Add(priceLineModel);
|
||||
CalculateTotalLine();
|
||||
|
||||
SaleGridModel model = new SaleGridModel();
|
||||
@@ -399,38 +437,14 @@ namespace Pos
|
||||
TotalPrice.IsEnabled = enabled;
|
||||
}
|
||||
|
||||
private void BuildAmuntDataGrid(decimal amount, string paymentMethod)
|
||||
private void BuildAmuntDataGrid(
|
||||
decimal? amount,
|
||||
PaymentMethod paymentMethod)
|
||||
{
|
||||
if (amount == -1)
|
||||
{
|
||||
amount = 0;
|
||||
foreach (PriceLineModel lineModel in _priceLineModels)
|
||||
{
|
||||
amount += (lineModel.Price * lineModel.NumberProducts);
|
||||
}
|
||||
}
|
||||
AmountGridModel amountGridModel =
|
||||
_salesTransactionService.AddPayment(paymentMethod, amount);
|
||||
|
||||
AmountGridModel amountGridModel = new AmountGridModel();
|
||||
amountGridModel.Amount = amount;
|
||||
amountGridModel.AmountText = $"Kr. {amount}";
|
||||
switch (paymentMethod)
|
||||
{
|
||||
case "PaymentMethod.Card":
|
||||
amountGridModel.PaymentMethodText = "Kort";
|
||||
break;
|
||||
|
||||
case "PaymentMethod.Cash":
|
||||
amountGridModel.PaymentMethodText = "Kontant";
|
||||
break;
|
||||
|
||||
case "PaymentMethod.GiftCard":
|
||||
amountGridModel.PaymentMethodText = "Gavekort";
|
||||
break;
|
||||
|
||||
case "PaymentMethod.MobilePay":
|
||||
amountGridModel.PaymentMethodText = "MobilePay";
|
||||
break;
|
||||
}
|
||||
amountGridModel.AmountText = $"Kr. {amountGridModel.Amount}";
|
||||
_paymentGridModels.Add(amountGridModel);
|
||||
|
||||
AmountPayed();
|
||||
@@ -438,28 +452,25 @@ namespace Pos
|
||||
|
||||
private void AmountPayed()
|
||||
{
|
||||
decimal amountPayed = 0;
|
||||
foreach (AmountGridModel paymentGridModel in _paymentGridModels)
|
||||
{
|
||||
amountPayed += paymentGridModel.Amount;
|
||||
}
|
||||
|
||||
if (amountPayed == _totalPrice)
|
||||
if (_salesTransactionService.AmountPaid ==
|
||||
_salesTransactionService.TotalPrice)
|
||||
{
|
||||
btnFinish_Click(null,null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (amountPayed > _totalPrice)
|
||||
if (_salesTransactionService.Change > 0)
|
||||
{
|
||||
lblRecived.Content = $"Byttepenge: Kr. {amountPayed - _totalPrice}";
|
||||
lblRecived.Content =
|
||||
$"Byttepenge: Kr. {_salesTransactionService.Change}";
|
||||
Style style = this.FindResource("BoldLabelWarningNoMargin") as Style;
|
||||
lblRecived.Style = style;
|
||||
btnFinish.IsEnabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
lblRecived.Content = $"At betale: Kr. {_totalPrice - amountPayed}";
|
||||
lblRecived.Content =
|
||||
$"At betale: Kr. {_salesTransactionService.RemainingAmount}";
|
||||
Style style = this.FindResource("BoldLabelNoMargin") as Style;
|
||||
lblRecived.Style = style;
|
||||
}
|
||||
@@ -473,21 +484,37 @@ namespace Pos
|
||||
if (_selectedSaleGridIndex == -1)
|
||||
return;
|
||||
int index = _selectedSaleGridIndex;
|
||||
//_saleGridModels.RemoveAt(_selectedSaleGridIndex);
|
||||
_priceLineModels.RemoveAt(index);
|
||||
_salesTransactionService.RemoveSaleLineAt(index);
|
||||
_saleGridModels.RemoveAt(index);
|
||||
_selectedSaleGridIndex = -1;
|
||||
CalculateTotalLine();
|
||||
|
||||
if (_salesTransactionService.AmountPaid > 0)
|
||||
AmountPayed();
|
||||
}
|
||||
}
|
||||
|
||||
private void btnFinish_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
_salesTransactionService.EnsureCanComplete();
|
||||
}
|
||||
catch (InvalidOperationException exception)
|
||||
{
|
||||
MessageBox.Show(
|
||||
exception.Message,
|
||||
"Salget kan ikke afsluttes",
|
||||
MessageBoxButton.OK,
|
||||
MessageBoxImage.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
SaleService saleService = new SaleService();
|
||||
saleService.Save(_saleGridModels.ToList(),_paymentGridModels.ToList(),_employeeId);
|
||||
_paymentGridModels.Clear();
|
||||
_priceLineModels.Clear();
|
||||
_salesTransactionService.Reset();
|
||||
UpdateWindowTitle();
|
||||
_totalPrice = 0;
|
||||
_saleGridModels.Clear();
|
||||
lblRecived.Content = string.Empty;
|
||||
_selectedSaleGridIndex = -1;
|
||||
|
||||
@@ -11,7 +11,7 @@ using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
using Database.Repository;
|
||||
using Pos.Service;
|
||||
|
||||
namespace Pos.Setting
|
||||
{
|
||||
@@ -20,6 +20,9 @@ namespace Pos.Setting
|
||||
/// </summary>
|
||||
public partial class AddEmployeeWindow : Window
|
||||
{
|
||||
private readonly EmployeeService _employeeService =
|
||||
new EmployeeService();
|
||||
|
||||
public AddEmployeeWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
@@ -27,9 +30,19 @@ namespace Pos.Setting
|
||||
|
||||
private void btnAdd_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
EmployeeRepository employeeRepository = new EmployeeRepository();
|
||||
employeeRepository.Add(txtStaff.Text);
|
||||
this.Close();
|
||||
try
|
||||
{
|
||||
_employeeService.AddEmployee(txtStaff.Text);
|
||||
Close();
|
||||
}
|
||||
catch (ArgumentException exception)
|
||||
{
|
||||
MessageBox.Show(
|
||||
exception.Message,
|
||||
"Ugyldig medarbejder",
|
||||
MessageBoxButton.OK,
|
||||
MessageBoxImage.Warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
using Database.Repository;
|
||||
using Pos.Service;
|
||||
|
||||
namespace Pos.Setting
|
||||
{
|
||||
@@ -21,6 +21,8 @@ namespace Pos.Setting
|
||||
public partial class DeleteEmployeeWindow : Window
|
||||
{
|
||||
private int _employeeId;
|
||||
private readonly EmployeeService _employeeService =
|
||||
new EmployeeService();
|
||||
|
||||
public DeleteEmployeeWindow(int employeeId, string staffName)
|
||||
{
|
||||
@@ -31,9 +33,8 @@ namespace Pos.Setting
|
||||
|
||||
private void btnYes_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
EmployeeRepository employeeRepository = new EmployeeRepository();
|
||||
employeeRepository.Delete(_employeeId);
|
||||
this.Close();
|
||||
_employeeService.ArchiveEmployee(_employeeId);
|
||||
Close();
|
||||
}
|
||||
|
||||
private void btnNo_Click(object sender, RoutedEventArgs e)
|
||||
|
||||
@@ -11,7 +11,7 @@ using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
using Database.Repository;
|
||||
using Pos.Service;
|
||||
|
||||
namespace Pos.Setting
|
||||
{
|
||||
@@ -21,6 +21,8 @@ namespace Pos.Setting
|
||||
public partial class EditEmployeeWindow : Window
|
||||
{
|
||||
private int _employeeId;
|
||||
private readonly EmployeeService _employeeService =
|
||||
new EmployeeService();
|
||||
|
||||
public EditEmployeeWindow(int employeeId, string staffName)
|
||||
{
|
||||
@@ -31,9 +33,21 @@ namespace Pos.Setting
|
||||
|
||||
private void btnEdit_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
EmployeeRepository employeeRepository = new EmployeeRepository();
|
||||
employeeRepository.Edit(_employeeId,txtEmployee.Text);
|
||||
this.Close();
|
||||
try
|
||||
{
|
||||
_employeeService.RenameEmployee(
|
||||
_employeeId,
|
||||
txtEmployee.Text);
|
||||
Close();
|
||||
}
|
||||
catch (ArgumentException exception)
|
||||
{
|
||||
MessageBox.Show(
|
||||
exception.Message,
|
||||
"Ugyldig medarbejder",
|
||||
MessageBoxButton.OK,
|
||||
MessageBoxImage.Warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
using Database.Models;
|
||||
using Database.Repository;
|
||||
using Pos.Service;
|
||||
|
||||
namespace Pos.Setting
|
||||
{
|
||||
@@ -24,6 +24,8 @@ namespace Pos.Setting
|
||||
{
|
||||
private int _selectedEmployee = -1;
|
||||
private string _employeeName = string.Empty;
|
||||
private readonly EmployeeService _employeeService =
|
||||
new EmployeeService();
|
||||
public EmployeeWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
@@ -62,15 +64,14 @@ namespace Pos.Setting
|
||||
deleteEmployeeWindow.Show();
|
||||
}
|
||||
|
||||
private void WindowClosed(object? sender, EventArgs e)
|
||||
private void WindowClosed(object sender, EventArgs e)
|
||||
{
|
||||
LoadData();
|
||||
}
|
||||
|
||||
private void LoadData()
|
||||
{
|
||||
using EmployeeRepository employeeRepository = new EmployeeRepository();
|
||||
List<EmployeeEntity> allStaff = employeeRepository.GetAll();
|
||||
List<EmployeeEntity> allStaff = _employeeService.GetEmployees();
|
||||
LstStaff.Items.Clear();
|
||||
foreach (EmployeeEntity staff in allStaff)
|
||||
{
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using Database.Repository;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using Pos.Service;
|
||||
|
||||
namespace Pos.Setting.ProductGroup
|
||||
{
|
||||
@@ -11,6 +11,11 @@ namespace Pos.Setting.ProductGroup
|
||||
/// </summary>
|
||||
public partial class AddProductWindow : Window
|
||||
{
|
||||
private readonly ProductGroupService _productGroupService =
|
||||
new ProductGroupService();
|
||||
private readonly ProductService _productService =
|
||||
new ProductService();
|
||||
|
||||
public AddProductWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
@@ -19,13 +24,16 @@ namespace Pos.Setting.ProductGroup
|
||||
|
||||
private void LoadData(int selectedIndex)
|
||||
{
|
||||
ProductGroupRepository productGroupRepository = new ProductGroupRepository();
|
||||
List<Database.Models.ProductGroupEntity> products = productGroupRepository.GetAll();
|
||||
foreach (Database.Models.ProductGroupEntity productGroup in products)
|
||||
List<Database.Models.ProductGroupEntity> productGroups =
|
||||
_productGroupService.GetProductGroups();
|
||||
|
||||
foreach (Database.Models.ProductGroupEntity productGroup in productGroups)
|
||||
{
|
||||
ComboBoxItem comboBoxItem = new ComboBoxItem();
|
||||
comboBoxItem.Tag = productGroup.Id;
|
||||
comboBoxItem.Content = productGroup.Name;
|
||||
ComboBoxItem comboBoxItem = new ComboBoxItem
|
||||
{
|
||||
Tag = productGroup.Id,
|
||||
Content = productGroup.Name
|
||||
};
|
||||
cmbProductGroup.Items.Add(comboBoxItem);
|
||||
}
|
||||
|
||||
@@ -34,12 +42,23 @@ namespace Pos.Setting.ProductGroup
|
||||
|
||||
private void btnAdd_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ComboBoxItem item = (ComboBoxItem) cmbProductGroup.SelectedItem;
|
||||
string name = txtProduct.Text;
|
||||
int id = Convert.ToInt32(item.Tag);
|
||||
ProductRepository productRepository = new ProductRepository();
|
||||
productRepository.Add(name,id);
|
||||
this.Close();
|
||||
ComboBoxItem item = (ComboBoxItem)cmbProductGroup.SelectedItem;
|
||||
|
||||
try
|
||||
{
|
||||
_productService.AddProduct(
|
||||
txtProduct.Text,
|
||||
Convert.ToInt32(item.Tag));
|
||||
Close();
|
||||
}
|
||||
catch (ArgumentException exception)
|
||||
{
|
||||
MessageBox.Show(
|
||||
exception.Message,
|
||||
"Ugyldigt produkt",
|
||||
MessageBoxButton.OK,
|
||||
MessageBoxImage.Warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
using Database.Repository;
|
||||
using Pos.Service;
|
||||
|
||||
namespace Pos.Setting.Product
|
||||
{
|
||||
@@ -21,20 +21,21 @@ namespace Pos.Setting.Product
|
||||
public partial class ArchiveProductWindow : Window
|
||||
{
|
||||
private int _productId;
|
||||
private readonly ProductService _productService =
|
||||
new ProductService();
|
||||
public ArchiveProductWindow(int productId)
|
||||
{
|
||||
InitializeComponent();
|
||||
_productId = productId;
|
||||
ProductRepository productRepository = new ProductRepository();
|
||||
Database.Models.ProductEntity product = productRepository.GetById(_productId);
|
||||
Database.Models.ProductEntity product =
|
||||
_productService.GetProduct(_productId);
|
||||
txtDelProduct.Text = product.Name;
|
||||
}
|
||||
|
||||
private void btnYes_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ProductRepository productRepository = new ProductRepository();
|
||||
productRepository.Archive(_productId);
|
||||
this.Close();
|
||||
_productService.ArchiveProduct(_productId);
|
||||
Close();
|
||||
}
|
||||
|
||||
private void btnNo_Click(object sender, RoutedEventArgs e)
|
||||
|
||||
@@ -12,7 +12,7 @@ using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
using Database.Repository;
|
||||
using Pos.Service;
|
||||
|
||||
namespace Pos.Setting.Product
|
||||
{
|
||||
@@ -23,6 +23,8 @@ namespace Pos.Setting.Product
|
||||
{
|
||||
ObservableCollection<Database.Models.ProductEntity> _productList = new ObservableCollection<Database.Models.ProductEntity>();
|
||||
private int _productGroupId;
|
||||
private readonly ProductService _productService =
|
||||
new ProductService();
|
||||
public ChangeProductOrderWindow(int productGroupId)
|
||||
{
|
||||
InitializeComponent();
|
||||
@@ -32,8 +34,8 @@ namespace Pos.Setting.Product
|
||||
|
||||
private void LoadData()
|
||||
{
|
||||
ProductRepository productRepository = new ProductRepository();
|
||||
List<Database.Models.ProductEntity> products = productRepository.GetByProductGroup(_productGroupId);
|
||||
List<Database.Models.ProductEntity> products =
|
||||
_productService.GetProducts(_productGroupId);
|
||||
foreach (Database.Models.ProductEntity product in products)
|
||||
{
|
||||
_productList.Add(product);
|
||||
@@ -86,13 +88,10 @@ namespace Pos.Setting.Product
|
||||
|
||||
private void btnSave_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ProductRepository productRepository = new ProductRepository();
|
||||
for (int i = 0; i < _productList.Count; i++)
|
||||
{
|
||||
Database.Models.ProductEntity product = _productList[i];
|
||||
productRepository.SetIndex(product.Id, i);
|
||||
}
|
||||
this.Close();
|
||||
_productService.UpdateOrder(
|
||||
_productGroupId,
|
||||
_productList.Select(product => product.Id).ToList());
|
||||
Close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
using Database.Repository;
|
||||
using Pos.Service;
|
||||
|
||||
namespace Pos.Setting.Product
|
||||
{
|
||||
@@ -23,6 +23,10 @@ namespace Pos.Setting.Product
|
||||
private int _productId;
|
||||
private int _selectedProductGroupId;
|
||||
private Database.Models.ProductEntity _product;
|
||||
private readonly ProductGroupService _productGroupService =
|
||||
new ProductGroupService();
|
||||
private readonly ProductService _productService =
|
||||
new ProductService();
|
||||
|
||||
public EditProductWindow(int productId, int selectedProductGroupId)
|
||||
{
|
||||
@@ -34,8 +38,8 @@ namespace Pos.Setting.Product
|
||||
|
||||
private void Init()
|
||||
{
|
||||
ProductGroupRepository categoryRepository = new ProductGroupRepository();
|
||||
List<Database.Models.ProductGroupEntity> productGroups = categoryRepository.GetAll();
|
||||
List<Database.Models.ProductGroupEntity> productGroups =
|
||||
_productGroupService.GetProductGroups();
|
||||
foreach (Database.Models.ProductGroupEntity productGroup in productGroups)
|
||||
{
|
||||
|
||||
@@ -46,17 +50,29 @@ namespace Pos.Setting.Product
|
||||
}
|
||||
cmbProductGroup.SelectedIndex = _selectedProductGroupId;
|
||||
|
||||
ProductRepository productRepository = new ProductRepository();
|
||||
_product = productRepository.GetById(_productId);
|
||||
_product = _productService.GetProduct(_productId);
|
||||
txtProduct.Text = _product.Name;
|
||||
}
|
||||
|
||||
private void btnEdit_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ComboBoxItem item = (ComboBoxItem)cmbProductGroup.SelectedItem;
|
||||
ProductRepository productRepository = new ProductRepository();
|
||||
productRepository.Update(_product.Id,(int)item.Tag,txtProduct.Text);
|
||||
this.Close();
|
||||
try
|
||||
{
|
||||
_productService.UpdateProduct(
|
||||
_product.Id,
|
||||
(int)item.Tag,
|
||||
txtProduct.Text);
|
||||
Close();
|
||||
}
|
||||
catch (ArgumentException exception)
|
||||
{
|
||||
MessageBox.Show(
|
||||
exception.Message,
|
||||
"Ugyldigt produkt",
|
||||
MessageBoxButton.OK,
|
||||
MessageBoxImage.Warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using Database.Repository;
|
||||
using Pos.Service;
|
||||
using Pos.Setting.Product;
|
||||
|
||||
namespace Pos.Setting.ProductGroup
|
||||
@@ -14,7 +14,10 @@ namespace Pos.Setting.ProductGroup
|
||||
public partial class ProductWindow : Window
|
||||
{
|
||||
private List<Database.Models.ProductGroupEntity> _productGroups;
|
||||
private int _productId;
|
||||
private readonly ProductGroupService _productGroupService =
|
||||
new ProductGroupService();
|
||||
private readonly ProductService _productService =
|
||||
new ProductService();
|
||||
|
||||
public ProductWindow()
|
||||
{
|
||||
@@ -26,8 +29,7 @@ namespace Pos.Setting.ProductGroup
|
||||
|
||||
private void Init()
|
||||
{
|
||||
ProductGroupRepository categoryRepository = new ProductGroupRepository();
|
||||
_productGroups = categoryRepository.GetAll();
|
||||
_productGroups = _productGroupService.GetProductGroups();
|
||||
cmbCat.Items.Clear();
|
||||
foreach (Database.Models.ProductGroupEntity productGroup in _productGroups)
|
||||
{
|
||||
@@ -38,14 +40,19 @@ namespace Pos.Setting.ProductGroup
|
||||
cmbCat.Items.Add(comboBoxItem);
|
||||
}
|
||||
|
||||
cmbCat.SelectedIndex = 0;
|
||||
if (_productGroups.Count > 0)
|
||||
cmbCat.SelectedIndex = 0;
|
||||
}
|
||||
|
||||
private void LoadData()
|
||||
{
|
||||
|
||||
ProductRepository productRepository = new ProductRepository();
|
||||
List<Database.Models.ProductEntity> products = productRepository.GetByProductGroup(_productGroups[cmbCat.SelectedIndex].Id);
|
||||
if (cmbCat.SelectedIndex < 0)
|
||||
return;
|
||||
|
||||
List<Database.Models.ProductEntity> products =
|
||||
_productService.GetProducts(
|
||||
_productGroups[cmbCat.SelectedIndex].Id);
|
||||
lstProductGroup.Items.Clear();
|
||||
foreach (Database.Models.ProductEntity product in products)
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@ using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
using Database.Repository;
|
||||
using Pos.Service;
|
||||
|
||||
namespace Pos.Setting.Category
|
||||
{
|
||||
@@ -20,6 +20,9 @@ namespace Pos.Setting.Category
|
||||
/// </summary>
|
||||
public partial class AddProductGroupWindow : Window
|
||||
{
|
||||
private readonly ProductGroupService _productGroupService =
|
||||
new ProductGroupService();
|
||||
|
||||
public AddProductGroupWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
@@ -27,9 +30,19 @@ namespace Pos.Setting.Category
|
||||
|
||||
private void btnAdd_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ProductGroupRepository categoryRepository = new ProductGroupRepository();
|
||||
categoryRepository.Add(txtProductGroup.Text);
|
||||
this.Close();
|
||||
try
|
||||
{
|
||||
_productGroupService.AddProductGroup(txtProductGroup.Text);
|
||||
Close();
|
||||
}
|
||||
catch (ArgumentException exception)
|
||||
{
|
||||
MessageBox.Show(
|
||||
exception.Message,
|
||||
"Ugyldig produktgruppe",
|
||||
MessageBoxButton.OK,
|
||||
MessageBoxImage.Warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
using Database.Repository;
|
||||
using Pos.Service;
|
||||
|
||||
namespace Pos.Setting.Category
|
||||
{
|
||||
@@ -21,6 +21,8 @@ namespace Pos.Setting.Category
|
||||
public partial class ArchiveProductGroupWindow : Window
|
||||
{
|
||||
private readonly int _id;
|
||||
private readonly ProductGroupService _productGroupService =
|
||||
new ProductGroupService();
|
||||
public ArchiveProductGroupWindow(string name, int id)
|
||||
{
|
||||
InitializeComponent();
|
||||
@@ -36,9 +38,8 @@ namespace Pos.Setting.Category
|
||||
|
||||
private void btnYes_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ProductGroupRepository categoryRepository = new ProductGroupRepository();
|
||||
categoryRepository.Archive(_id);
|
||||
this.Close();
|
||||
_productGroupService.ArchiveProductGroup(_id);
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
using Database.Repository;
|
||||
using Pos.Service;
|
||||
|
||||
namespace Pos.Setting.Category
|
||||
{
|
||||
@@ -22,6 +22,8 @@ namespace Pos.Setting.Category
|
||||
public partial class ChangeProductGroupOrderWindow : Window
|
||||
{
|
||||
ObservableCollection<Database.Models.ProductGroupEntity> _prodGroupList = new ObservableCollection<Database.Models.ProductGroupEntity>();
|
||||
private readonly ProductGroupService _productGroupService =
|
||||
new ProductGroupService();
|
||||
public ChangeProductGroupOrderWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
@@ -30,8 +32,8 @@ namespace Pos.Setting.Category
|
||||
|
||||
private void LoadData()
|
||||
{
|
||||
ProductGroupRepository categoryRepository = new ProductGroupRepository();
|
||||
List<Database.Models.ProductGroupEntity> categories = categoryRepository.GetAll();
|
||||
List<Database.Models.ProductGroupEntity> categories =
|
||||
_productGroupService.GetProductGroups();
|
||||
foreach (Database.Models.ProductGroupEntity category in categories)
|
||||
{
|
||||
_prodGroupList.Add(category);
|
||||
@@ -84,13 +86,9 @@ namespace Pos.Setting.Category
|
||||
|
||||
private void btnSave_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ProductGroupRepository categoryRepository = new ProductGroupRepository();
|
||||
for (int i = 0; i < _prodGroupList.Count; i++)
|
||||
{
|
||||
Database.Models.ProductGroupEntity productGroup = _prodGroupList[i];
|
||||
categoryRepository.SetIndex(productGroup.Id,i);
|
||||
}
|
||||
this.Close();
|
||||
_productGroupService.UpdateOrder(
|
||||
_prodGroupList.Select(productGroup => productGroup.Id).ToList());
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
using Database.Repository;
|
||||
using Pos.Service;
|
||||
|
||||
namespace Pos.Setting.Category
|
||||
{
|
||||
@@ -21,6 +21,8 @@ namespace Pos.Setting.Category
|
||||
public partial class EditProductGroupWindow : Window
|
||||
{
|
||||
private readonly int _id;
|
||||
private readonly ProductGroupService _productGroupService =
|
||||
new ProductGroupService();
|
||||
|
||||
public EditProductGroupWindow(string name, int id)
|
||||
{
|
||||
@@ -31,9 +33,21 @@ namespace Pos.Setting.Category
|
||||
|
||||
private void btnEdit_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ProductGroupRepository categoryRepository = new ProductGroupRepository();
|
||||
categoryRepository.Edit(txtProductGroup.Text,_id);
|
||||
this.Close();
|
||||
try
|
||||
{
|
||||
_productGroupService.RenameProductGroup(
|
||||
_id,
|
||||
txtProductGroup.Text);
|
||||
Close();
|
||||
}
|
||||
catch (ArgumentException exception)
|
||||
{
|
||||
MessageBox.Show(
|
||||
exception.Message,
|
||||
"Ugyldig produktgruppe",
|
||||
MessageBoxButton.OK,
|
||||
MessageBoxImage.Warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
using Database.Repository;
|
||||
using Pos.Service;
|
||||
using Pos.Setting.Category;
|
||||
|
||||
namespace Pos.Category
|
||||
@@ -23,6 +23,8 @@ namespace Pos.Category
|
||||
{
|
||||
private int _id = -1;
|
||||
private string _name = string.Empty;
|
||||
private readonly ProductGroupService _productGroupService =
|
||||
new ProductGroupService();
|
||||
|
||||
public ProductGroupWindow()
|
||||
{
|
||||
@@ -53,8 +55,8 @@ namespace Pos.Category
|
||||
|
||||
private void LoadData()
|
||||
{
|
||||
ProductGroupRepository categoryRepository = new ProductGroupRepository();
|
||||
List<Database.Models.ProductGroupEntity> categories = categoryRepository.GetAll();
|
||||
List<Database.Models.ProductGroupEntity> categories =
|
||||
_productGroupService.GetProductGroups();
|
||||
LstCategory.Items.Clear();
|
||||
foreach (Database.Models.ProductGroupEntity category in categories)
|
||||
{
|
||||
|
||||
@@ -11,8 +11,8 @@ using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
using Database.Repository;
|
||||
using Pos.Category;
|
||||
using Pos.Service;
|
||||
using Pos.Setting.ProductGroup;
|
||||
|
||||
namespace Pos.Setting
|
||||
@@ -22,6 +22,9 @@ namespace Pos.Setting
|
||||
/// </summary>
|
||||
public partial class SettingWindow : Window
|
||||
{
|
||||
private readonly ProductGroupService _productGroupService =
|
||||
new ProductGroupService();
|
||||
|
||||
public SettingWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
@@ -42,10 +45,8 @@ namespace Pos.Setting
|
||||
|
||||
private void btnProductGroups_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ProductWindow productWindow = new ProductWindow();
|
||||
//Check if there is any categories, if not close this window with a message.
|
||||
ProductGroupRepository categoryRepository = new ProductGroupRepository();
|
||||
bool anyExist = categoryRepository.Any();
|
||||
bool anyExist = _productGroupService.HasProductGroups();
|
||||
if (!anyExist)
|
||||
{
|
||||
MessageBox.Show("Der er ingen kategorier, opret dem først", "Kategorier", MessageBoxButton.OK,
|
||||
@@ -53,6 +54,7 @@ namespace Pos.Setting
|
||||
}
|
||||
else
|
||||
{
|
||||
ProductWindow productWindow = new ProductWindow();
|
||||
productWindow.Show();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
|
||||
//public class Rootobject
|
||||
//{
|
||||
// public string logoBase64 { get; set; }
|
||||
// public Header[] header { get; set; }
|
||||
// public Bodymodel bodyModel { get; set; }
|
||||
// public Footer[] footer { get; set; }
|
||||
//}
|
||||
|
||||
//public class Bodymodel
|
||||
//{
|
||||
// public Product[] products { get; set; }
|
||||
// public int totalPrice { get; set; }
|
||||
// public int totalVat { get; set; }
|
||||
// public int receiptNumber { get; set; }
|
||||
// public string receiptTime { get; set; }
|
||||
// public string staff { get; set; }
|
||||
//}
|
||||
|
||||
//public class Product
|
||||
//{
|
||||
// public string product { get; set; }
|
||||
// public string noOfProduct { get; set; }
|
||||
// public float price { get; set; }
|
||||
// public float totalPrice { get; set; }
|
||||
//}
|
||||
|
||||
//public class Header
|
||||
//{
|
||||
// public string value { get; set; }
|
||||
// public Printstyles printStyles { get; set; }
|
||||
// public int textAlignment { get; set; }
|
||||
// public int feedingLines { get; set; }
|
||||
//}
|
||||
|
||||
//public class Printstyles
|
||||
//{
|
||||
// public bool fontB { get; set; }
|
||||
// public bool bold { get; set; }
|
||||
// public bool doubleHeight { get; set; }
|
||||
// public bool doubleWidth { get; set; }
|
||||
// public bool underline { get; set; }
|
||||
//}
|
||||
|
||||
//public class Footer
|
||||
//{
|
||||
// public string value { get; set; }
|
||||
// public Printstyles1 printStyles { get; set; }
|
||||
// public int textAlignment { get; set; }
|
||||
// public int feedingLines { get; set; }
|
||||
//}
|
||||
|
||||
//public class Printstyles1
|
||||
//{
|
||||
// public bool fontB { get; set; }
|
||||
// public bool bold { get; set; }
|
||||
// public bool doubleHeight { get; set; }
|
||||
// public bool doubleWidth { get; set; }
|
||||
// public bool underline { get; set; }
|
||||
//}
|
||||
23
PointOfSale/Pos.Web/Components/App.razor
Normal file
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<base href="/" />
|
||||
<ResourcePreloader />
|
||||
<link rel="stylesheet" href="@Assets["app.css"]" />
|
||||
<link rel="stylesheet" href="@Assets["Pos.Web.styles.css"]" />
|
||||
<ImportMap />
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
||||
<HeadOutlet @rendermode="InteractiveServer" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<Routes @rendermode="InteractiveServer" />
|
||||
<ReconnectModal />
|
||||
<script src="@Assets["_framework/blazor.web.js"]"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
16
PointOfSale/Pos.Web/Components/Layout/MainLayout.razor
Normal file
@@ -0,0 +1,16 @@
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
<FluentLayout>
|
||||
<FluentLayoutItem Area="LayoutArea.Content" Class="main">
|
||||
<div class="content">
|
||||
@Body
|
||||
</div>
|
||||
</FluentLayoutItem>
|
||||
</FluentLayout>
|
||||
<FluentDialogProvider />
|
||||
|
||||
<div id="blazor-error-ui" data-nosnippet>
|
||||
An unhandled error has occurred.
|
||||
<a href="." class="reload">Reload</a>
|
||||
<span class="dismiss">🗙</span>
|
||||
</div>
|
||||
31
PointOfSale/Pos.Web/Components/Layout/ReconnectModal.razor
Normal file
@@ -0,0 +1,31 @@
|
||||
<script type="module" src="@Assets["Components/Layout/ReconnectModal.razor.js"]"></script>
|
||||
|
||||
<dialog id="components-reconnect-modal" data-nosnippet>
|
||||
<div class="components-reconnect-container">
|
||||
<div class="components-rejoining-animation" aria-hidden="true">
|
||||
<div></div>
|
||||
<div></div>
|
||||
</div>
|
||||
<p class="components-reconnect-first-attempt-visible">
|
||||
Rejoining the server...
|
||||
</p>
|
||||
<p class="components-reconnect-repeated-attempt-visible">
|
||||
Rejoin failed... trying again in <span id="components-seconds-to-next-attempt"></span> seconds.
|
||||
</p>
|
||||
<p class="components-reconnect-failed-visible">
|
||||
Failed to rejoin.<br />Please retry or reload the page.
|
||||
</p>
|
||||
<button id="components-reconnect-button" class="components-reconnect-failed-visible">
|
||||
Retry
|
||||
</button>
|
||||
<p class="components-pause-visible">
|
||||
The session has been paused by the server.
|
||||
</p>
|
||||
<p class="components-resume-failed-visible">
|
||||
Failed to resume the session.<br />Please retry or reload the page.
|
||||
</p>
|
||||
<button id="components-resume-button" class="components-pause-visible components-resume-failed-visible">
|
||||
Resume
|
||||
</button>
|
||||
</div>
|
||||
</dialog>
|
||||
157
PointOfSale/Pos.Web/Components/Layout/ReconnectModal.razor.css
Normal file
@@ -0,0 +1,157 @@
|
||||
.components-reconnect-first-attempt-visible,
|
||||
.components-reconnect-repeated-attempt-visible,
|
||||
.components-reconnect-failed-visible,
|
||||
.components-pause-visible,
|
||||
.components-resume-failed-visible,
|
||||
.components-rejoining-animation {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#components-reconnect-modal.components-reconnect-show .components-reconnect-first-attempt-visible,
|
||||
#components-reconnect-modal.components-reconnect-show .components-rejoining-animation,
|
||||
#components-reconnect-modal.components-reconnect-paused .components-pause-visible,
|
||||
#components-reconnect-modal.components-reconnect-resume-failed .components-resume-failed-visible,
|
||||
#components-reconnect-modal.components-reconnect-retrying,
|
||||
#components-reconnect-modal.components-reconnect-retrying .components-reconnect-repeated-attempt-visible,
|
||||
#components-reconnect-modal.components-reconnect-retrying .components-rejoining-animation,
|
||||
#components-reconnect-modal.components-reconnect-failed,
|
||||
#components-reconnect-modal.components-reconnect-failed .components-reconnect-failed-visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
#components-reconnect-modal {
|
||||
background-color: white;
|
||||
width: 20rem;
|
||||
margin: 20vh auto;
|
||||
padding: 2rem;
|
||||
border: 0;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 3px 6px 2px rgba(0, 0, 0, 0.3);
|
||||
opacity: 0;
|
||||
transition: display 0.5s allow-discrete, overlay 0.5s allow-discrete;
|
||||
animation: components-reconnect-modal-fadeOutOpacity 0.5s both;
|
||||
&[open]
|
||||
|
||||
{
|
||||
animation: components-reconnect-modal-slideUp 1.5s cubic-bezier(.05, .89, .25, 1.02) 0.3s, components-reconnect-modal-fadeInOpacity 0.5s ease-in-out 0.3s;
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#components-reconnect-modal::backdrop {
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
animation: components-reconnect-modal-fadeInOpacity 0.5s ease-in-out;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@keyframes components-reconnect-modal-slideUp {
|
||||
0% {
|
||||
transform: translateY(30px) scale(0.95);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes components-reconnect-modal-fadeInOpacity {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes components-reconnect-modal-fadeOutOpacity {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.components-reconnect-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
#components-reconnect-modal p {
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#components-reconnect-modal button {
|
||||
border: 0;
|
||||
background-color: #6b9ed2;
|
||||
color: white;
|
||||
padding: 4px 24px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
#components-reconnect-modal button:hover {
|
||||
background-color: #3b6ea2;
|
||||
}
|
||||
|
||||
#components-reconnect-modal button:active {
|
||||
background-color: #6b9ed2;
|
||||
}
|
||||
|
||||
.components-rejoining-animation {
|
||||
position: relative;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.components-rejoining-animation div {
|
||||
position: absolute;
|
||||
border: 3px solid #0087ff;
|
||||
opacity: 1;
|
||||
border-radius: 50%;
|
||||
animation: components-rejoining-animation 1.5s cubic-bezier(0, 0.2, 0.8, 1) infinite;
|
||||
}
|
||||
|
||||
.components-rejoining-animation div:nth-child(2) {
|
||||
animation-delay: -0.5s;
|
||||
}
|
||||
|
||||
@keyframes components-rejoining-animation {
|
||||
0% {
|
||||
top: 40px;
|
||||
left: 40px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
4.9% {
|
||||
top: 40px;
|
||||
left: 40px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
5% {
|
||||
top: 40px;
|
||||
left: 40px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// Set up event handlers
|
||||
const reconnectModal = document.getElementById("components-reconnect-modal");
|
||||
reconnectModal.addEventListener("components-reconnect-state-changed", handleReconnectStateChanged);
|
||||
|
||||
const retryButton = document.getElementById("components-reconnect-button");
|
||||
retryButton.addEventListener("click", retry);
|
||||
|
||||
const resumeButton = document.getElementById("components-resume-button");
|
||||
resumeButton.addEventListener("click", resume);
|
||||
|
||||
function handleReconnectStateChanged(event) {
|
||||
if (event.detail.state === "show") {
|
||||
reconnectModal.showModal();
|
||||
} else if (event.detail.state === "hide") {
|
||||
reconnectModal.close();
|
||||
} else if (event.detail.state === "failed") {
|
||||
document.addEventListener("visibilitychange", retryWhenDocumentBecomesVisible);
|
||||
} else if (event.detail.state === "rejected") {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
async function retry() {
|
||||
document.removeEventListener("visibilitychange", retryWhenDocumentBecomesVisible);
|
||||
|
||||
try {
|
||||
// Reconnect will asynchronously return:
|
||||
// - true to mean success
|
||||
// - false to mean we reached the server, but it rejected the connection (e.g., unknown circuit ID)
|
||||
// - exception to mean we didn't reach the server (this can be sync or async)
|
||||
const successful = await Blazor.reconnect();
|
||||
if (!successful) {
|
||||
// We have been able to reach the server, but the circuit is no longer available.
|
||||
// We'll reload the page so the user can continue using the app as quickly as possible.
|
||||
const resumeSuccessful = await Blazor.resumeCircuit();
|
||||
if (!resumeSuccessful) {
|
||||
location.reload();
|
||||
} else {
|
||||
reconnectModal.close();
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
// We got an exception, server is currently unavailable
|
||||
document.addEventListener("visibilitychange", retryWhenDocumentBecomesVisible);
|
||||
}
|
||||
}
|
||||
|
||||
async function resume() {
|
||||
try {
|
||||
const successful = await Blazor.resumeCircuit();
|
||||
if (!successful) {
|
||||
location.reload();
|
||||
}
|
||||
} catch {
|
||||
reconnectModal.classList.replace("components-reconnect-paused", "components-reconnect-resume-failed");
|
||||
}
|
||||
}
|
||||
|
||||
async function retryWhenDocumentBecomesVisible() {
|
||||
if (document.visibilityState === "visible") {
|
||||
await retry();
|
||||
}
|
||||
}
|
||||
36
PointOfSale/Pos.Web/Components/Pages/Error.razor
Normal file
@@ -0,0 +1,36 @@
|
||||
@page "/Error"
|
||||
@using System.Diagnostics
|
||||
|
||||
<PageTitle>Error</PageTitle>
|
||||
|
||||
<h1 class="text-danger">Error.</h1>
|
||||
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
||||
|
||||
@if (ShowRequestId)
|
||||
{
|
||||
<p>
|
||||
<strong>Request ID:</strong> <code>@RequestId</code>
|
||||
</p>
|
||||
}
|
||||
|
||||
<h3>Development Mode</h3>
|
||||
<p>
|
||||
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
|
||||
</p>
|
||||
<p>
|
||||
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
|
||||
It can result in displaying sensitive information from exceptions to end users.
|
||||
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
|
||||
and restarting the app.
|
||||
</p>
|
||||
|
||||
@code{
|
||||
[CascadingParameter]
|
||||
private HttpContext? HttpContext { get; set; }
|
||||
|
||||
private string? RequestId { get; set; }
|
||||
private bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
||||
|
||||
protected override void OnInitialized() =>
|
||||
RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier;
|
||||
}
|
||||
21
PointOfSale/Pos.Web/Components/Pages/Home.razor
Normal file
@@ -0,0 +1,21 @@
|
||||
@rendermode InteractiveServer
|
||||
@inject NavigationManager Nav
|
||||
@page "/"
|
||||
|
||||
<Header/>
|
||||
|
||||
|
||||
<div class="home-container">
|
||||
<FluentButton Class="menu-button" Size="ButtonSize.Large">
|
||||
Kassesalg
|
||||
</FluentButton>
|
||||
<FluentButton Class="menu-button" Size="ButtonSize.Large">
|
||||
Dagens salg
|
||||
</FluentButton>
|
||||
<FluentButton Class="menu-button" Size="ButtonSize.Large">
|
||||
Print Bon
|
||||
</FluentButton>
|
||||
<FluentButton Class="menu-button" Size="ButtonSize.Large" onclick="@(() => Nav.NavigateTo("/settings"))">
|
||||
Indstillinger
|
||||
</FluentButton>
|
||||
</div>
|
||||
14
PointOfSale/Pos.Web/Components/Pages/Home.razor.css
Normal file
@@ -0,0 +1,14 @@
|
||||
.home-container {
|
||||
width: min(100%, 1100px);
|
||||
margin: 32px auto;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.home-container ::deep .menu-button {
|
||||
width: 100%;
|
||||
min-height: 220px;
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
}
|
||||
5
PointOfSale/Pos.Web/Components/Pages/NotFound.razor
Normal file
@@ -0,0 +1,5 @@
|
||||
@page "/not-found"
|
||||
@layout MainLayout
|
||||
|
||||
<h3>Not Found</h3>
|
||||
<p>Sorry, the content you are looking for does not exist.</p>
|
||||
@@ -0,0 +1,44 @@
|
||||
@using Pos.Service
|
||||
@inject EmployeeService EmployeeService
|
||||
|
||||
@if (IsOpen)
|
||||
{
|
||||
<div class="dialog-overlay">
|
||||
<div class="employee-dialog" role="dialog" aria-modal="true" aria-labelledby="create-employee-title">
|
||||
<div class="dialog-header">
|
||||
<h2 id="create-employee-title">Opret medarbejder</h2>
|
||||
<button class="close-button"
|
||||
type="button"
|
||||
aria-label="Luk"
|
||||
@onclick="CloseAsync">
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="dialog-content">
|
||||
<label for="employee-name">Navn</label>
|
||||
<input id="employee-name"
|
||||
class="name-input"
|
||||
type="text"
|
||||
placeholder="Indtast navn"
|
||||
@bind="_name"
|
||||
@bind:event="oninput" />
|
||||
<p>Medarbejderen bliver aktiv med det samme.</p>
|
||||
</div>
|
||||
|
||||
<div class="dialog-actions">
|
||||
<button class="cancel-button"
|
||||
type="button"
|
||||
@onclick="CloseAsync">
|
||||
Annuller
|
||||
</button>
|
||||
<button class="create-button"
|
||||
type="button"
|
||||
disabled="@string.IsNullOrWhiteSpace(_name)"
|
||||
@onclick="CreateEmployeeAsync">
|
||||
Opret medarbejder
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using Database.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Pos.Service;
|
||||
|
||||
namespace Pos.Web.Components.Pages.Settings
|
||||
{
|
||||
public partial class CreateEmployeeDialog
|
||||
{
|
||||
[Parameter]
|
||||
public bool IsOpen { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<bool> IsOpenChanged { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<EmployeeEntity> EmployeeCreated { get; set; }
|
||||
|
||||
private string _name = string.Empty;
|
||||
|
||||
private async Task CreateEmployeeAsync()
|
||||
{
|
||||
EmployeeEntity employee =
|
||||
await EmployeeService.AddEmployeeAsync(_name);
|
||||
|
||||
await EmployeeCreated.InvokeAsync(employee);
|
||||
await CloseAsync();
|
||||
}
|
||||
|
||||
private async Task CloseAsync()
|
||||
{
|
||||
_name = string.Empty;
|
||||
await IsOpenChanged.InvokeAsync(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
.dialog-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 1000;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: 24px;
|
||||
background: rgb(15 23 42 / 45%);
|
||||
}
|
||||
|
||||
.employee-dialog {
|
||||
width: min(100%, 650px);
|
||||
overflow: hidden;
|
||||
border-radius: 12px;
|
||||
background: white;
|
||||
color: #242424;
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
.dialog-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 28px 40px 18px;
|
||||
}
|
||||
|
||||
.dialog-header h2 {
|
||||
margin: 0;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.close-button {
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: #334155;
|
||||
cursor: pointer;
|
||||
font-size: 36px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.dialog-content {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
padding: 18px 40px 36px;
|
||||
}
|
||||
|
||||
.dialog-content label {
|
||||
color: #242424;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.name-input {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
padding: 0 14px;
|
||||
border: 1px solid #8a8a8a;
|
||||
border-radius: 6px;
|
||||
outline: none;
|
||||
background: #ffffff;
|
||||
color: #242424;
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
.name-input::placeholder {
|
||||
color: #707070;
|
||||
}
|
||||
|
||||
.name-input:focus {
|
||||
border-color: #0f6cbd;
|
||||
box-shadow: 0 0 0 1px #0f6cbd;
|
||||
}
|
||||
|
||||
.dialog-content p {
|
||||
margin: 6px 0 0;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.dialog-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 16px;
|
||||
padding: 24px 40px;
|
||||
border-top: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
.cancel-button,
|
||||
.create-button {
|
||||
min-height: 42px;
|
||||
padding: 0 22px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.cancel-button {
|
||||
border: 1px solid #616161;
|
||||
background: #ffffff;
|
||||
color: #242424;
|
||||
}
|
||||
|
||||
.cancel-button:hover {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.create-button {
|
||||
border: 1px solid #0f6cbd;
|
||||
background: #0f6cbd;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.create-button:hover:not(:disabled) {
|
||||
background: #115ea3;
|
||||
}
|
||||
|
||||
.create-button:disabled {
|
||||
border-color: #d1d1d1;
|
||||
background: #e0e0e0;
|
||||
color: #707070;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.dialog-header,
|
||||
.dialog-content,
|
||||
.dialog-actions {
|
||||
padding-right: 24px;
|
||||
padding-left: 24px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
@page "/settings/employees"
|
||||
@inject IDialogService DialogService
|
||||
@rendermode InteractiveServer
|
||||
@using Database.Models
|
||||
@using Pos.Service
|
||||
@inject EmployeeService EmployeeService
|
||||
|
||||
<Header />
|
||||
|
||||
<div class="employees-page">
|
||||
<div class="employees-header">
|
||||
<h1>Medarbejdere</h1>
|
||||
|
||||
<FluentButton Appearance="@ButtonAppearance.Primary"
|
||||
OnClick="@(() => _showCreateDialog = true)">
|
||||
Opret medarbejder
|
||||
</FluentButton>
|
||||
</div>
|
||||
@if (_isLoading)
|
||||
{
|
||||
<FluentProgressRing />
|
||||
}
|
||||
else
|
||||
{
|
||||
<FluentDataGrid TGridItem="EmployeeEntity"
|
||||
Items="@_employees.AsQueryable()"
|
||||
GridTemplateColumns="1fr 180px">
|
||||
|
||||
<PropertyColumn Property="@(item => item.Name)"
|
||||
Title="Navn"
|
||||
Sortable="true" />
|
||||
|
||||
<TemplateColumn Title="Handling" Context="employee">
|
||||
<FluentButton Class="deactivate-button"
|
||||
Appearance="@ButtonAppearance.Outline"
|
||||
OnClick="@(() => ConfirmDeactivation(employee))">
|
||||
Deaktivér
|
||||
</FluentButton>
|
||||
</TemplateColumn>
|
||||
</FluentDataGrid>
|
||||
}
|
||||
</div>
|
||||
|
||||
<CreateEmployeeDialog @bind-IsOpen="_showCreateDialog"
|
||||
EmployeeCreated="EmployeeCreated" />
|
||||
@@ -0,0 +1,53 @@
|
||||
using Database.Models;
|
||||
using Microsoft.FluentUI.AspNetCore.Components;
|
||||
|
||||
namespace Pos.Web.Components.Pages.Settings
|
||||
{
|
||||
public partial class Employees
|
||||
{
|
||||
private List<EmployeeEntity> _employees = [];
|
||||
private bool _isLoading = true;
|
||||
private bool _showCreateDialog;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_employees = await EmployeeService.GetEmployeesAsync();
|
||||
_isLoading = false;
|
||||
}
|
||||
|
||||
private void LoadEmployees()
|
||||
{
|
||||
_employees = EmployeeService.GetEmployees();
|
||||
}
|
||||
|
||||
private async Task ConfirmDeactivation(EmployeeEntity employee)
|
||||
{
|
||||
DialogResult result = await DialogService.ShowConfirmationAsync(
|
||||
$"Er du sikker på, at du vil deaktivere {employee.Name}?",
|
||||
"Bekræft deaktivering",
|
||||
"Annuller",
|
||||
"Deaktivér");
|
||||
|
||||
if (result.Cancelled)
|
||||
{
|
||||
DeactivateEmployee(employee);
|
||||
}
|
||||
}
|
||||
|
||||
private void DeactivateEmployee(EmployeeEntity employee)
|
||||
{
|
||||
EmployeeService.ArchiveEmployee(employee.Id);
|
||||
LoadEmployees();
|
||||
}
|
||||
|
||||
private class EmployeeRow
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
private void EmployeeCreated(EmployeeEntity employee)
|
||||
{
|
||||
_employees.Add(employee);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/* CSS for Employees component */
|
||||
.employees-page {
|
||||
width: min(100%, 1100px);
|
||||
margin: 0 auto;
|
||||
padding: 32px;
|
||||
}
|
||||
|
||||
.employees-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.employees-header h1 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.employees-page ::deep fluent-data-grid {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.employees-page ::deep .deactivate-button {
|
||||
color: #b10e1e;
|
||||
border-color: #b10e1e;
|
||||
}
|
||||
20
PointOfSale/Pos.Web/Components/Pages/Settings/Settings.razor
Normal file
@@ -0,0 +1,20 @@
|
||||
@rendermode InteractiveServer
|
||||
@inject NavigationManager Nav
|
||||
@page "/settings"
|
||||
|
||||
<Header />
|
||||
<div class="settings-page">
|
||||
<h1>Indstillinger</h1>
|
||||
|
||||
<p>Administrer data, der bruges i kassesalget.</p>
|
||||
|
||||
<div class="settings-grid">
|
||||
<FluentButton Class="settings-button" onclick="@(() => Nav.NavigateTo("/settings/employees"))">
|
||||
Medarbejder
|
||||
</FluentButton>
|
||||
|
||||
<FluentButton Class="settings-button">
|
||||
Vare
|
||||
</FluentButton>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,24 @@
|
||||
.settings-page {
|
||||
width: min(100%, 1100px);
|
||||
margin: 0 auto;
|
||||
padding: 32px;
|
||||
}
|
||||
|
||||
.settings-page h1 {
|
||||
margin-bottom: 12px;
|
||||
color: var(--colorNeutralForeground1);
|
||||
}
|
||||
|
||||
.settings-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 24px;
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
.settings-grid ::deep .settings-button {
|
||||
width: 100%;
|
||||
min-height: 220px;
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
}
|
||||
5
PointOfSale/Pos.Web/Components/Routes.razor
Normal file
@@ -0,0 +1,5 @@
|
||||
<Router AppAssembly="typeof(Program).Assembly" NotFoundPage="typeof(Pages.NotFound)">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
|
||||
</Found>
|
||||
</Router>
|
||||
12
PointOfSale/Pos.Web/Components/Shared/Header.razor
Normal file
@@ -0,0 +1,12 @@
|
||||
@rendermode InteractiveServer
|
||||
@implements IDisposable
|
||||
|
||||
<header class="top-nav">
|
||||
<div class="brand">Blomster POS</div>
|
||||
|
||||
<time datetime="@_currentTime.ToString("O")">
|
||||
@_currentTime.ToString(
|
||||
"dddd d. MMMM yyyy · HH:mm",
|
||||
_danishCulture)
|
||||
</time>
|
||||
</header>
|
||||
33
PointOfSale/Pos.Web/Components/Shared/Header.razor.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System.Globalization;
|
||||
|
||||
namespace Pos.Web.Components.Shared
|
||||
{
|
||||
public partial class Header
|
||||
{
|
||||
private readonly CultureInfo _danishCulture =
|
||||
CultureInfo.GetCultureInfo("da-DK");
|
||||
|
||||
private readonly Timer _timer;
|
||||
private DateTime _currentTime = DateTime.Now;
|
||||
|
||||
public Header()
|
||||
{
|
||||
_timer = new Timer(
|
||||
UpdateTime,
|
||||
null,
|
||||
TimeSpan.FromMinutes(1),
|
||||
TimeSpan.FromMinutes(1));
|
||||
}
|
||||
|
||||
private void UpdateTime(object? state)
|
||||
{
|
||||
_currentTime = DateTime.Now;
|
||||
_ = InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_timer.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
24
PointOfSale/Pos.Web/Components/Shared/Header.razor.css
Normal file
@@ -0,0 +1,24 @@
|
||||
/* CSS for Header component */
|
||||
.top-nav {
|
||||
min-height: 72px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 24px;
|
||||
padding: 0 32px;
|
||||
background: var(--colorNeutralBackground1);
|
||||
border-bottom: 1px solid var(--colorNeutralStroke2);
|
||||
}
|
||||
|
||||
.brand {
|
||||
color: var(--colorNeutralForeground1);
|
||||
font-size: 26px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
time {
|
||||
color: var(--colorNeutralForeground2);
|
||||
font-size: 18px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
14
PointOfSale/Pos.Web/Components/_Imports.razor
Normal file
@@ -0,0 +1,14 @@
|
||||
@using System.Net.Http
|
||||
@using System.Net.Http.Json
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||
@using Microsoft.FluentUI.AspNetCore.Components
|
||||
@using Icons = Microsoft.FluentUI.AspNetCore.Components.Icons
|
||||
@using Microsoft.JSInterop
|
||||
@using Pos.Web
|
||||
@using Pos.Web.Components
|
||||
@using Pos.Web.Components.Layout
|
||||
@using Pos.Web.Components.Shared
|
||||
21
PointOfSale/Pos.Web/Pos.Web.csproj
Normal file
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<BlazorDisableThrowNavigationException>true</BlazorDisableThrowNavigationException>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.FluentUI.AspNetCore.Components" Version="5.0.0-rc.4-26180.1" />
|
||||
<PackageReference Include="Microsoft.FluentUI.AspNetCore.Components.Icons" Version="4.14.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Pos.Models\Pos.Models.csproj" />
|
||||
<ProjectReference Include="..\Pos.ServiceDefaults\Pos.ServiceDefaults.csproj" />
|
||||
<ProjectReference Include="..\Pos.Service\Pos.Service.csproj" />
|
||||
<ProjectReference Include="..\Pos.Ui\Database\Database.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
35
PointOfSale/Pos.Web/Program.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using Microsoft.FluentUI.AspNetCore.Components;
|
||||
using Pos.Web.Components;
|
||||
using Pos.Service;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.AddServiceDefaults();
|
||||
|
||||
// Add services to the container.
|
||||
builder.Services.AddRazorComponents()
|
||||
.AddInteractiveServerComponents();
|
||||
builder.Services.AddFluentUIComponents();
|
||||
builder.Services.AddScoped<EmployeeService>();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
app.MapDefaultEndpoints();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (!app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseExceptionHandler("/Error", createScopeForErrors: true);
|
||||
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
||||
app.UseHsts();
|
||||
}
|
||||
app.UseStatusCodePagesWithReExecute("/not-found", createScopeForStatusCodePages: true);
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
app.UseAntiforgery();
|
||||
|
||||
app.MapStaticAssets();
|
||||
app.MapRazorComponents<App>()
|
||||
.AddInteractiveServerRenderMode();
|
||||
|
||||
app.Run();
|
||||
23
PointOfSale/Pos.Web/Properties/launchSettings.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "http://localhost:5281",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "https://localhost:7102;http://localhost:5281",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
8
PointOfSale/Pos.Web/appsettings.Development.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
10
PointOfSale/Pos.Web/appsettings.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"SqlServer": "Server=(localdb)\\MSSQLLocalDB;Database=pointofsale;Trusted_Connection=True;TrustServerCertificate=True;",
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
89
PointOfSale/Pos.Web/wwwroot/app.css
Normal file
@@ -0,0 +1,89 @@
|
||||
@import '/_content/Microsoft.FluentUI.AspNetCore.Components/css/reboot.css';
|
||||
|
||||
body {
|
||||
--body-font: "Segoe UI Variable", "Segoe UI", sans-serif;
|
||||
margin: 0;
|
||||
font-family: var(--body-font);
|
||||
font-size: var(--type-ramp-base-font-size);
|
||||
line-height: var(--type-ramp-base-line-height);
|
||||
}
|
||||
|
||||
.main {
|
||||
min-height: 100dvh;
|
||||
color: var(--neutral-foreground-rest);
|
||||
align-items: stretch !important;
|
||||
}
|
||||
|
||||
.content {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
padding: 0.5rem 1.5rem;
|
||||
align-self: stretch !important;
|
||||
}
|
||||
|
||||
#blazor-error-ui {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 1000;
|
||||
box-sizing: border-box;
|
||||
display: none;
|
||||
width: 100%;
|
||||
padding: 0.6rem 1.25rem 0.7rem;
|
||||
background: lightyellow;
|
||||
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
#blazor-error-ui .dismiss {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: 0.75rem;
|
||||
top: 0.5rem;
|
||||
}
|
||||
|
||||
.blazor-error-boundary {
|
||||
padding: 1rem 1rem 1rem 3.7rem;
|
||||
color: white;
|
||||
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
|
||||
}
|
||||
|
||||
.blazor-error-boundary::before {
|
||||
content: "An error has occurred. ";
|
||||
}
|
||||
|
||||
.loading-progress {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 8rem;
|
||||
height: 8rem;
|
||||
margin: 20vh auto 1rem;
|
||||
}
|
||||
|
||||
.loading-progress circle {
|
||||
fill: none;
|
||||
stroke: #e0e0e0;
|
||||
stroke-width: 0.6rem;
|
||||
transform-origin: 50% 50%;
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.loading-progress circle:last-child {
|
||||
stroke: #1b6ec2;
|
||||
stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%;
|
||||
transition: stroke-dasharray 0.05s ease-in-out;
|
||||
}
|
||||
|
||||
.loading-progress-text {
|
||||
position: absolute;
|
||||
inset: calc(20vh + 3.25rem) 0 auto 0.2rem;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.loading-progress-text::after {
|
||||
content: var(--blazor-load-percentage-text, "Loading");
|
||||
}
|
||||
|
||||
code {
|
||||
color: #c02d76;
|
||||
}
|
||||
BIN
PointOfSale/Pos.Web/wwwroot/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
@@ -13,12 +13,18 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EpsonPrinter", "EpsonPrinte
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pos.AppHost", "Pos.AppHost\Pos.AppHost.csproj", "{53EFB4EB-931D-419B-8399-4E389011076C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pos.ServiceDefaults", "Pos.ServiceDefaults\Pos.ServiceDefaults.csproj", "{EDC882E6-4983-35BD-3357-E433A3E64369}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Pos.Test", "Pos.Test", "{9051E89F-15CC-40A4-8C60-82C98151139E}"
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{9051E89F-15CC-40A4-8C60-82C98151139E}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IntegrationTests", "IntegrationTests\IntegrationTests.csproj", "{04551552-086E-4CF4-AFD3-A4E51CD36182}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pos.Web", "Pos.Web\Pos.Web.csproj", "{81AEF9BF-15E9-414D-9F18-345631896022}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pos.Service", "Pos.Service\Pos.Service.csproj", "{563C2EC1-B243-4A66-BE53-88A0BA216706}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pos.Models", "Pos.Models\Pos.Models.csproj", "{D6EA5236-CEFB-4889-9E67-4983D1A016FA}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pos.ServiceDefaults", "Pos.ServiceDefaults\Pos.ServiceDefaults.csproj", "{EDC882E6-4983-35BD-3357-E433A3E64369}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -41,14 +47,26 @@ Global
|
||||
{53EFB4EB-931D-419B-8399-4E389011076C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{53EFB4EB-931D-419B-8399-4E389011076C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{53EFB4EB-931D-419B-8399-4E389011076C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{EDC882E6-4983-35BD-3357-E433A3E64369}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{EDC882E6-4983-35BD-3357-E433A3E64369}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{EDC882E6-4983-35BD-3357-E433A3E64369}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{EDC882E6-4983-35BD-3357-E433A3E64369}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{04551552-086E-4CF4-AFD3-A4E51CD36182}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{04551552-086E-4CF4-AFD3-A4E51CD36182}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{04551552-086E-4CF4-AFD3-A4E51CD36182}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{04551552-086E-4CF4-AFD3-A4E51CD36182}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{81AEF9BF-15E9-414D-9F18-345631896022}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{81AEF9BF-15E9-414D-9F18-345631896022}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{81AEF9BF-15E9-414D-9F18-345631896022}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{81AEF9BF-15E9-414D-9F18-345631896022}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{563C2EC1-B243-4A66-BE53-88A0BA216706}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{563C2EC1-B243-4A66-BE53-88A0BA216706}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{563C2EC1-B243-4A66-BE53-88A0BA216706}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{563C2EC1-B243-4A66-BE53-88A0BA216706}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{D6EA5236-CEFB-4889-9E67-4983D1A016FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D6EA5236-CEFB-4889-9E67-4983D1A016FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D6EA5236-CEFB-4889-9E67-4983D1A016FA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D6EA5236-CEFB-4889-9E67-4983D1A016FA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{EDC882E6-4983-35BD-3357-E433A3E64369}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{EDC882E6-4983-35BD-3357-E433A3E64369}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{EDC882E6-4983-35BD-3357-E433A3E64369}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{EDC882E6-4983-35BD-3357-E433A3E64369}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
BIN
layout/DagensSalg.png
Normal file
|
After Width: | Height: | Size: 887 KiB |
BIN
layout/Forside.png
Normal file
|
After Width: | Height: | Size: 852 KiB |
BIN
layout/Indstillinger.png
Normal file
|
After Width: | Height: | Size: 864 KiB |
BIN
layout/Indstillinger/Medarbejder.png
Normal file
|
After Width: | Height: | Size: 922 KiB |
BIN
layout/Indstillinger/OpretMedarbejder.png
Normal file
|
After Width: | Height: | Size: 1011 KiB |
BIN
layout/Indstillinger/Vare.png
Normal file
|
After Width: | Height: | Size: 1019 KiB |
BIN
layout/PrintBon.png
Normal file
|
After Width: | Height: | Size: 931 KiB |
BIN
layout/Salg/BetalingByttepenge.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |