Compare commits
18 Commits
3414400efe
...
feature/bl
| Author | SHA1 | Date | |
|---|---|---|---|
| 770e170d0e | |||
| 8de6ff9338 | |||
|
|
048f45a4a1 | ||
|
|
01d81cf14f | ||
|
|
49a9f336b1 | ||
| 8953e917e1 | |||
| 6e14130a9c | |||
| f01f05e126 | |||
|
|
c1695b4967 | ||
|
|
205d27c6c7 | ||
|
|
03594bba3f | ||
|
|
2e537337f5 | ||
|
|
9497afe262 | ||
|
|
6df185d519 | ||
|
|
97688542fe | ||
|
|
236eb6792c | ||
|
|
d02414edbb | ||
| be0350f458 |
35
PointOfSale/.dockerignore
Normal file
35
PointOfSale/.dockerignore
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
**/.classpath
|
||||||
|
**/.dockerignore
|
||||||
|
**/.env
|
||||||
|
**/.git
|
||||||
|
**/.gitignore
|
||||||
|
**/.project
|
||||||
|
**/.settings
|
||||||
|
**/.toolstarget
|
||||||
|
**/.vs
|
||||||
|
**/.vscode
|
||||||
|
**/*.*proj.user
|
||||||
|
**/*.dbmdl
|
||||||
|
**/*.jfm
|
||||||
|
**/azds.yaml
|
||||||
|
**/bin
|
||||||
|
**/charts
|
||||||
|
**/docker-compose*
|
||||||
|
**/Dockerfile*
|
||||||
|
**/node_modules
|
||||||
|
**/npm-debug.log
|
||||||
|
**/obj
|
||||||
|
**/secrets.dev.yaml
|
||||||
|
**/values.dev.yaml
|
||||||
|
LICENSE
|
||||||
|
README.md
|
||||||
|
!**/.gitignore
|
||||||
|
!.git/HEAD
|
||||||
|
!.git/config
|
||||||
|
!.git/packed-refs
|
||||||
|
!.git/refs/heads/**
|
||||||
|
database-backup/
|
||||||
|
.env
|
||||||
|
|
||||||
|
database-data/
|
||||||
|
|
||||||
7
PointOfSale/.gitignore
vendored
7
PointOfSale/.gitignore
vendored
@@ -360,3 +360,10 @@ MigrationBackup/
|
|||||||
|
|
||||||
# Fody - auto-generated XML schema
|
# Fody - auto-generated XML schema
|
||||||
FodyWeavers.xsd
|
FodyWeavers.xsd
|
||||||
|
|
||||||
|
# Local Docker database data and secrets
|
||||||
|
.env
|
||||||
|
database-backup/
|
||||||
|
|
||||||
|
database-data/
|
||||||
|
|
||||||
|
|||||||
@@ -20,4 +20,8 @@
|
|||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.7" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.7" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Pos.ServiceDefaults\Pos.ServiceDefaults.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
@@ -4,6 +4,8 @@ using Microsoft.Extensions.DependencyInjection;
|
|||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
builder.AddServiceDefaults();
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
builder.Services.AddControllers();
|
builder.Services.AddControllers();
|
||||||
builder.Services.AddEndpointsApiExplorer();
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
@@ -13,6 +15,8 @@ builder.Services.AddScoped<EpsonPrintService>();
|
|||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
|
app.MapDefaultEndpoints();
|
||||||
|
|
||||||
// Swagger (altid aktiv – vi er på en Pi, ikke i produktion)
|
// Swagger (altid aktiv – vi er på en Pi, ikke i produktion)
|
||||||
app.UseSwagger();
|
app.UseSwagger();
|
||||||
app.UseSwaggerUI();
|
app.UseSwaggerUI();
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
},
|
},
|
||||||
"EpsonPrinter": {
|
"EpsonPrinter": {
|
||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"dotnetRunMessages": "true",
|
"dotnetRunMessages": true,
|
||||||
"launchBrowser": true,
|
"launchBrowser": true,
|
||||||
"launchUrl": "swagger",
|
"launchUrl": "swagger",
|
||||||
"applicationUrl": "https://localhost:5001;http://localhost:5000",
|
"applicationUrl": "https://localhost:5001;http://localhost:5000",
|
||||||
|
|||||||
Binary file not shown.
@@ -10,3 +10,9 @@ Buket..............1 á 50,50 DKK : 50,50 DKK
|
|||||||
Total: 50,50 DKK
|
Total: 50,50 DKK
|
||||||
Moms udgør 25% : 12,63 DKK
|
Moms udgør 25% : 12,63 DKK
|
||||||
Tak for handlen
|
Tak for handlen
|
||||||
|
Dato: 30-07-2026
|
||||||
|
-----------------------------------
|
||||||
|
string : .00
|
||||||
|
-----------------------------------
|
||||||
|
Total salg: .00
|
||||||
|
Antal kunder: 0
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
var builder = DistributedApplication.CreateBuilder(args);
|
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();
|
builder.Build().Run();
|
||||||
|
|||||||
@@ -9,7 +9,8 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Pos.Ui\Database\Database.csproj" />
|
<ProjectReference Include="..\EpsonPrinterLinux\EpsonPrinter.csproj" />
|
||||||
|
<ProjectReference Include="..\Pos.Web\Pos.Web.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ namespace Pos.Models
|
|||||||
{
|
{
|
||||||
public class AmountGridModel
|
public class AmountGridModel
|
||||||
{
|
{
|
||||||
public string PaymentMethodText { get; set; }
|
public string PaymentMethodText { get; set; } = string.Empty;
|
||||||
public decimal Amount { get; set; }
|
public decimal Amount { get; set; }
|
||||||
public string AmountText { get; set; }
|
public string AmountText { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,11 +8,11 @@ namespace Pos.Json
|
|||||||
{
|
{
|
||||||
public class BodyModel
|
public class BodyModel
|
||||||
{
|
{
|
||||||
public ProductModel[] products { get; set; }
|
public ProductModel[] products { get; set; } = Array.Empty<ProductModel>();
|
||||||
public float totalPrice { get; set; }
|
public float totalPrice { get; set; }
|
||||||
public float totalVat { get; set; }
|
public float totalVat { get; set; }
|
||||||
public int receiptNumber { get; set; }
|
public int receiptNumber { get; set; }
|
||||||
public string receiptTime { get; set; }
|
public string receiptTime { get; set; } = string.Empty;
|
||||||
public string staff { get; set; }
|
public string staff { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,8 +8,8 @@ namespace Pos.Json
|
|||||||
{
|
{
|
||||||
public class FooterModel
|
public class FooterModel
|
||||||
{
|
{
|
||||||
public string value { get; set; }
|
public string value { get; set; } = string.Empty;
|
||||||
public PrintStylesModel printStyles { get; set; }
|
public PrintStylesModel printStyles { get; set; } = new();
|
||||||
public int textAlignment { get; set; }
|
public int textAlignment { get; set; }
|
||||||
public int feedingLines { get; set; }
|
public int feedingLines { get; set; }
|
||||||
}
|
}
|
||||||
@@ -8,8 +8,8 @@ namespace Pos.Json
|
|||||||
{
|
{
|
||||||
public class HeaderModel
|
public class HeaderModel
|
||||||
{
|
{
|
||||||
public string value { get; set; }
|
public string value { get; set; } = string.Empty;
|
||||||
public PrintStylesModel printStyles { get; set; }
|
public PrintStylesModel printStyles { get; set; } = new();
|
||||||
public int textAlignment { get; set; }
|
public int textAlignment { get; set; }
|
||||||
public int feedingLines { get; set; }
|
public int feedingLines { get; set; }
|
||||||
}
|
}
|
||||||
@@ -8,9 +8,9 @@ namespace Pos.Json
|
|||||||
{
|
{
|
||||||
public class PosReceipt
|
public class PosReceipt
|
||||||
{
|
{
|
||||||
public string logoBase64 { get; set; }
|
public string logoBase64 { get; set; } = string.Empty;
|
||||||
public HeaderModel[] header { get; set; }
|
public HeaderModel[] header { get; set; } = Array.Empty<HeaderModel>();
|
||||||
public BodyModel bodyModel { get; set; } = new();
|
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 class ProductModel
|
||||||
{
|
{
|
||||||
public string product { get; set; }
|
public string product { get; set; } = string.Empty;
|
||||||
public string noOfProduct { get; set; }
|
public string noOfProduct { get; set; } = string.Empty;
|
||||||
public float price { get; set; }
|
public float price { get; set; }
|
||||||
public float totalPrice { get; set; }
|
public float totalPrice { get; set; }
|
||||||
}
|
}
|
||||||
@@ -13,7 +13,7 @@ namespace Pos.Json
|
|||||||
|
|
||||||
public class SaleOfDayDetail
|
public class SaleOfDayDetail
|
||||||
{
|
{
|
||||||
public string Category { get; set; }
|
public string Category { get; set; } = string.Empty;
|
||||||
public decimal TotalSale { get; set; }
|
public decimal TotalSale { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
9
PointOfSale/Pos.Models/Pos.Models.csproj
Normal file
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 class PriceLineModel
|
||||||
{
|
{
|
||||||
public string Name { get; set; }
|
public string Name { get; set; } = string.Empty;
|
||||||
public int NumberProducts { get; set; }
|
public int NumberProducts { get; set; }
|
||||||
public decimal Price { get; set; }
|
public decimal Price { get; set; }
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
11
PointOfSale/Pos.Models/ReceiptDetailModel.cs
Normal file
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
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
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 class SaleGridModel
|
||||||
{
|
{
|
||||||
public string Navn { get; set; }
|
public string Navn { get; set; } = string.Empty;
|
||||||
public string Stk { get; set; }
|
public string Stk { get; set; } = string.Empty;
|
||||||
public string Pris { get; set; }
|
public string Pris { get; set; } = string.Empty;
|
||||||
public string Total { get; set; }
|
public string Total { get; set; } = string.Empty;
|
||||||
public SaleGridInternalModel SaleGridInternalModel { get; set; }
|
public SaleGridInternalModel SaleGridInternalModel { get; set; } = new();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,8 +8,8 @@ namespace Pos.Models
|
|||||||
{
|
{
|
||||||
public class TotalSaleCategory
|
public class TotalSaleCategory
|
||||||
{
|
{
|
||||||
public string Category { get; set; }
|
public string Category { get; set; } = string.Empty;
|
||||||
public decimal Sale { get; set; }
|
public decimal Sale { get; set; }
|
||||||
public string SaleString { get; set; }
|
public string SaleString { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
11
PointOfSale/Pos.Models/TotalSaleDetail.cs
Normal file
11
PointOfSale/Pos.Models/TotalSaleDetail.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Pos.Models;
|
||||||
|
|
||||||
|
public class TotalSaleDetail
|
||||||
|
{
|
||||||
|
public decimal TotalSale { get; set; }
|
||||||
|
public int TotalCustomer { get; set; }
|
||||||
|
public List<TotalSaleCategory> TotalSaleCategories { get; set; } = new();
|
||||||
|
public List<TotalSalePayment> PaymentBreakdown { get; set; } = new();
|
||||||
|
}
|
||||||
7
PointOfSale/Pos.Models/TotalSalePayment.cs
Normal file
7
PointOfSale/Pos.Models/TotalSalePayment.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Pos.Models;
|
||||||
|
|
||||||
|
public class TotalSalePayment
|
||||||
|
{
|
||||||
|
public string PaymentMethod { get; set; } = string.Empty;
|
||||||
|
public decimal Amount { get; set; }
|
||||||
|
}
|
||||||
@@ -10,8 +10,8 @@ namespace Pos.Service
|
|||||||
|
|
||||||
private static bool _invalidEmployee = false;
|
private static bool _invalidEmployee = false;
|
||||||
private static bool _invalidProductGroup = false;
|
private static bool _invalidProductGroup = false;
|
||||||
private static List<EmployeeEntity> _employees;
|
private static List<EmployeeEntity> _employees = new();
|
||||||
private static List<ProductGroupEntity> _productGroups;
|
private static List<ProductGroupEntity> _productGroups = new();
|
||||||
|
|
||||||
|
|
||||||
public static void Invalidate()
|
public static void Invalidate()
|
||||||
67
PointOfSale/Pos.Service/EmployeeService.cs
Normal file
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
PointOfSale/Pos.Service/IProductService.cs
Normal file
14
PointOfSale/Pos.Service/IProductService.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Database.Models;
|
||||||
|
|
||||||
|
namespace Pos.Service;
|
||||||
|
|
||||||
|
public interface IProductService
|
||||||
|
{
|
||||||
|
List<ProductEntity> GetProducts(int productGroupId);
|
||||||
|
ProductEntity GetProduct(int productId);
|
||||||
|
void AddProduct(string name, int productGroupId);
|
||||||
|
void UpdateProduct(int productId, int productGroupId, string name);
|
||||||
|
void ArchiveProduct(int productId);
|
||||||
|
void UpdateOrder(int productGroupId, IReadOnlyList<int> orderedProductIds);
|
||||||
|
}
|
||||||
19
PointOfSale/Pos.Service/Pos.Service.csproj
Normal file
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>
|
||||||
112
PointOfSale/Pos.Service/ProductGroupService.cs
Normal file
112
PointOfSale/Pos.Service/ProductGroupService.cs
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
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, decimal vatRate)
|
||||||
|
{
|
||||||
|
_productGroupRepository.Add(
|
||||||
|
ValidateName(name),
|
||||||
|
ValidateVatRate(vatRate));
|
||||||
|
CacheService.Invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddProductGroup(string name)
|
||||||
|
{
|
||||||
|
AddProductGroup(name, 25m);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RenameProductGroup(int productGroupId, string name)
|
||||||
|
{
|
||||||
|
ProductGroupEntity productGroup = _productGroupRepository.Get(productGroupId);
|
||||||
|
UpdateProductGroup(productGroupId, name, productGroup.VatRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateProductGroup(
|
||||||
|
int productGroupId,
|
||||||
|
string name,
|
||||||
|
decimal vatRate)
|
||||||
|
{
|
||||||
|
ValidateId(productGroupId);
|
||||||
|
_productGroupRepository.Edit(
|
||||||
|
ValidateName(name),
|
||||||
|
ValidateVatRate(vatRate),
|
||||||
|
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 decimal ValidateVatRate(decimal vatRate)
|
||||||
|
{
|
||||||
|
if (vatRate < 0 || vatRate > 100)
|
||||||
|
throw new ArgumentOutOfRangeException(
|
||||||
|
nameof(vatRate),
|
||||||
|
"Momsen skal være mellem 0 og 100 procent.");
|
||||||
|
|
||||||
|
return decimal.Round(vatRate, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
97
PointOfSale/Pos.Service/ProductService.cs
Normal file
97
PointOfSale/Pos.Service/ProductService.cs
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
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
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Configuration;
|
using System.Configuration;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -84,7 +84,9 @@ namespace Pos.Service
|
|||||||
|
|
||||||
LoadConfig loadConfig = new LoadConfig();
|
LoadConfig loadConfig = new LoadConfig();
|
||||||
IConfiguration config = loadConfig.ByEnvironment();
|
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");
|
RestClient restClient = new RestClient($"{url}/api/PosPrinter/SaleOfDay");
|
||||||
RestRequest request = new RestRequest();
|
RestRequest request = new RestRequest();
|
||||||
@@ -101,6 +103,34 @@ namespace Pos.Service
|
|||||||
foreach (SaleEntity sale in sales)
|
foreach (SaleEntity sale in sales)
|
||||||
{
|
{
|
||||||
List<SaleLineEntity> saleLineBySaleId = _saleRepository.GetSaleLineBySaleId(sale.Id);
|
List<SaleLineEntity> saleLineBySaleId = _saleRepository.GetSaleLineBySaleId(sale.Id);
|
||||||
|
List<PaymentEntity> paymentsBySaleId =
|
||||||
|
_saleRepository.GetPaymentBySaleId(sale.Id);
|
||||||
|
|
||||||
|
foreach (PaymentEntity payment in paymentsBySaleId)
|
||||||
|
{
|
||||||
|
string paymentMethod = string.IsNullOrWhiteSpace(payment.Type)
|
||||||
|
? "Ukendt"
|
||||||
|
: payment.Type;
|
||||||
|
|
||||||
|
TotalSalePayment? paymentSummary =
|
||||||
|
totalSaleDetail.PaymentBreakdown
|
||||||
|
.FirstOrDefault(item =>
|
||||||
|
item.PaymentMethod == paymentMethod);
|
||||||
|
|
||||||
|
if (paymentSummary is null)
|
||||||
|
{
|
||||||
|
totalSaleDetail.PaymentBreakdown.Add(
|
||||||
|
new TotalSalePayment
|
||||||
|
{
|
||||||
|
PaymentMethod = paymentMethod,
|
||||||
|
Amount = payment.Amount
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
paymentSummary.Amount += payment.Amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
foreach (SaleLineEntity saleLine in saleLineBySaleId)
|
foreach (SaleLineEntity saleLine in saleLineBySaleId)
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -159,7 +189,9 @@ namespace Pos.Service
|
|||||||
|
|
||||||
LoadConfig loadConfig = new LoadConfig();
|
LoadConfig loadConfig = new LoadConfig();
|
||||||
IConfiguration config = loadConfig.ByEnvironment();
|
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";
|
string url = $"{apiUrl}/api/PosPrinter/Receipt";
|
||||||
|
|
||||||
RestClient restClient = new RestClient(url);
|
RestClient restClient = new RestClient(url);
|
||||||
@@ -299,3 +331,4 @@ namespace Pos.Service
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
171
PointOfSale/Pos.Service/SalesTransactionService.cs
Normal file
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">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<IsAspireSharedProject>true</IsAspireSharedProject>
|
<IsAspireSharedProject>true</IsAspireSharedProject>
|
||||||
|
|||||||
@@ -7,6 +7,10 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.18" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.18" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.18" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.18" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.18">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.10" />
|
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.10" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.10" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.10" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="10.0.10" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="10.0.10" />
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@@ -58,3 +58,7 @@ namespace Pos
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
namespace Database.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(PosDbContext))]
|
||||||
|
[Migration("20260827000000_AddProductGroupVatRate")]
|
||||||
|
public class AddProductGroupVatRate : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<decimal>(
|
||||||
|
name: "VatRate",
|
||||||
|
table: "productgroup",
|
||||||
|
type: "decimal(5,2)",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 25m);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "VatRate",
|
||||||
|
table: "productgroup");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ namespace Database.Models
|
|||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public bool IsArchived { get; set; }
|
public bool IsArchived { get; set; }
|
||||||
public int Index { get; set; }
|
public int Index { get; set; }
|
||||||
|
public decimal VatRate { get; set; }
|
||||||
|
|
||||||
public ICollection<ProductEntity> Products { get; set; }
|
public ICollection<ProductEntity> Products { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Database.Models;
|
using Database.Models;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@@ -17,14 +17,24 @@ namespace Database
|
|||||||
public DbSet<SaleLineEntity> SalesLines { get; set; }
|
public DbSet<SaleLineEntity> SalesLines { get; set; }
|
||||||
public DbSet<PaymentEntity> Payment { 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(
|
protected override void OnConfiguring(
|
||||||
DbContextOptionsBuilder optionsBuilder)
|
DbContextOptionsBuilder optionsBuilder)
|
||||||
{
|
{
|
||||||
LoadConfig l = new LoadConfig();
|
LoadConfig l = new LoadConfig();
|
||||||
IConfiguration config = l.ByEnvironment();
|
IConfiguration config = l.ByEnvironment();
|
||||||
string connectionString = config["SqlServer"].ToString();
|
string connectionString = System.Environment.GetEnvironmentVariable("SqlServer") ?? config["SqlServer"].ToString();
|
||||||
optionsBuilder
|
optionsBuilder
|
||||||
.UseSqlServer(connectionString)
|
.UseSqlServer(connectionString, sqlOptions => sqlOptions.EnableRetryOnFailure())
|
||||||
.UseLoggerFactory(LoggerFactory.Create(b => b
|
.UseLoggerFactory(LoggerFactory.Create(b => b
|
||||||
.AddFilter(level => level >= LogLevel.Information)))
|
.AddFilter(level => level >= LogLevel.Information)))
|
||||||
.EnableSensitiveDataLogging()
|
.EnableSensitiveDataLogging()
|
||||||
@@ -33,3 +43,6 @@ namespace Database
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
using System;
|
using Database.Models;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Database.Models;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
|
|
||||||
namespace Database.Repository
|
namespace Database.Repository
|
||||||
{
|
{
|
||||||
@@ -58,5 +59,32 @@ namespace Database.Repository
|
|||||||
public void Dispose()
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,10 +28,11 @@ namespace Database.Repository
|
|||||||
return product;
|
return product;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Add(string name)
|
public void Add(string name, decimal vatRate)
|
||||||
{
|
{
|
||||||
ProductGroupEntity productGroup = new ProductGroupEntity();
|
ProductGroupEntity productGroup = new ProductGroupEntity();
|
||||||
productGroup.Name = name;
|
productGroup.Name = name;
|
||||||
|
productGroup.VatRate = vatRate;
|
||||||
using PosDbContext context = new PosDbContext();
|
using PosDbContext context = new PosDbContext();
|
||||||
//Get the highest index
|
//Get the highest index
|
||||||
ProductGroupEntity highest = context.ProductGroups.OrderByDescending(c => c.Index).Take(1).FirstOrDefault();
|
ProductGroupEntity highest = context.ProductGroups.OrderByDescending(c => c.Index).Take(1).FirstOrDefault();
|
||||||
@@ -45,11 +46,12 @@ namespace Database.Repository
|
|||||||
context.SaveChanges();
|
context.SaveChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Edit(string name, int id)
|
public void Edit(string name, decimal vatRate, int id)
|
||||||
{
|
{
|
||||||
using PosDbContext context = new PosDbContext();
|
using PosDbContext context = new PosDbContext();
|
||||||
ProductGroupEntity productGroup = context.ProductGroups.Single(c => c.Id == id);
|
ProductGroupEntity productGroup = context.ProductGroups.Single(c => c.Id == id);
|
||||||
productGroup.Name = name;
|
productGroup.Name = name;
|
||||||
|
productGroup.VatRate = vatRate;
|
||||||
context.SaveChanges();
|
context.SaveChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,10 +71,30 @@ namespace Database.Repository
|
|||||||
context.SaveChanges();
|
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()
|
public bool Any()
|
||||||
{
|
{
|
||||||
using PosDbContext context = new PosDbContext();
|
using PosDbContext context = new PosDbContext();
|
||||||
bool any = context.ProductGroups.Any();
|
bool any = context.ProductGroups.Any(c => !c.IsArchived);
|
||||||
return any;
|
return any;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Database.Models;
|
using Database.Models;
|
||||||
|
|
||||||
@@ -38,6 +39,33 @@ namespace Database.Repository
|
|||||||
context.SaveChanges();
|
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)
|
public ProductEntity GetById(int id)
|
||||||
{
|
{
|
||||||
using PosDbContext context = new PosDbContext();
|
using PosDbContext context = new PosDbContext();
|
||||||
@@ -72,7 +100,11 @@ namespace Database.Repository
|
|||||||
product.ProductGroupId = productGroupId;
|
product.ProductGroupId = productGroupId;
|
||||||
using PosDbContext context = new PosDbContext();
|
using PosDbContext context = new PosDbContext();
|
||||||
//Get the highest index
|
//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)
|
if (highest == null)
|
||||||
{
|
{
|
||||||
highest = new ProductEntity();
|
highest = new ProductEntity();
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Pos.Models
|
|
||||||
{
|
|
||||||
public class TotalSaleDetail
|
|
||||||
{
|
|
||||||
public decimal TotalSale { get; set; }
|
|
||||||
public int TotalCustomer { get; set; }
|
|
||||||
public List<TotalSaleCategory> TotalSaleCategories { get; set; } = new();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -18,12 +18,10 @@
|
|||||||
<None Remove="Icons\settings.png" />
|
<None Remove="Icons\settings.png" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="RestSharp" Version="108.0.3" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Database\Database.csproj" />
|
<ProjectReference Include="..\Database\Database.csproj" />
|
||||||
|
<ProjectReference Include="..\..\Pos.Models\Pos.Models.csproj" />
|
||||||
|
<ProjectReference Include="..\..\Pos.Service\Pos.Service.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -11,8 +11,7 @@ using System.Windows.Input;
|
|||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
using System.Windows.Shapes;
|
using System.Windows.Shapes;
|
||||||
using Database.Models;
|
using Pos.Models;
|
||||||
using Database.Repository;
|
|
||||||
using Pos.Service;
|
using Pos.Service;
|
||||||
|
|
||||||
namespace Pos
|
namespace Pos
|
||||||
@@ -25,6 +24,9 @@ namespace Pos
|
|||||||
private List<ListReceiptModel> _receiptList = new List<ListReceiptModel>();
|
private List<ListReceiptModel> _receiptList = new List<ListReceiptModel>();
|
||||||
private bool _init = false;
|
private bool _init = false;
|
||||||
private int _saleId = 0;
|
private int _saleId = 0;
|
||||||
|
private readonly SaleHistoryService _saleHistoryService =
|
||||||
|
new SaleHistoryService();
|
||||||
|
private readonly SaleService _saleService = new SaleService();
|
||||||
|
|
||||||
public PrintReceiptWindow()
|
public PrintReceiptWindow()
|
||||||
{
|
{
|
||||||
@@ -38,28 +40,39 @@ namespace Pos
|
|||||||
|
|
||||||
private void Search_OnClick(object sender, RoutedEventArgs e)
|
private void Search_OnClick(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
DateTime startDate = (DateTime)StartDatePicker.SelectedDate;
|
if (StartDatePicker.SelectedDate is not DateTime startDate ||
|
||||||
DateTime endDate = (DateTime)EndDatePicker.SelectedDate;
|
EndDatePicker.SelectedDate is not DateTime endDate)
|
||||||
SaleRepository saleRepository = new SaleRepository();
|
return;
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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
|
ListReceiptModel receiptModel = new ListReceiptModel
|
||||||
{
|
{
|
||||||
Id = entity.Id,
|
Id = receipt.SaleId,
|
||||||
Content = $"{entity.Time:dd-MM-yyyy HH:mm} - Pris: {total:0.00}"
|
Content =
|
||||||
|
$"{receipt.SaleTime:dd-MM-yyyy HH:mm} - Pris: {receipt.Total:0.00}"
|
||||||
};
|
};
|
||||||
_receiptList.Add(receiptModel);
|
_receiptList.Add(receiptModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
_init = true;
|
_init = true;
|
||||||
|
ListReciept.ItemsSource = null;
|
||||||
ListReciept.ItemsSource = _receiptList;
|
ListReciept.ItemsSource = _receiptList;
|
||||||
_init = false;
|
_init = false;
|
||||||
}
|
}
|
||||||
@@ -78,23 +91,21 @@ namespace Pos
|
|||||||
|
|
||||||
private void ListReciept_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
|
private void ListReciept_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||||
{
|
{
|
||||||
if (_init)
|
if (_init || e.AddedItems.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ListReceiptModel data = (ListReceiptModel) e.AddedItems[0];
|
ListReceiptModel data = (ListReceiptModel) e.AddedItems[0];
|
||||||
_saleId = data.Id;
|
_saleId = data.Id;
|
||||||
SaleRepository saleRepository = new SaleRepository();
|
ReceiptDetailModel receipt =
|
||||||
List<SaleLineEntity> saleLineBySaleId = saleRepository.GetSaleLineBySaleId(data.Id);
|
_saleHistoryService.GetReceipt(data.Id);
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
decimal total = 0;
|
foreach (ReceiptLineModel saleLine in receipt.Lines)
|
||||||
foreach (SaleLineEntity saleLine in saleLineBySaleId)
|
|
||||||
{
|
{
|
||||||
total += saleLine.Total;
|
|
||||||
sb.AppendLine(
|
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;
|
PrintReceipt.Visibility = Visibility.Visible;
|
||||||
SaleLines.Clear();
|
SaleLines.Clear();
|
||||||
SaleLines.Text = sb.ToString();
|
SaleLines.Text = sb.ToString();
|
||||||
@@ -102,8 +113,7 @@ namespace Pos
|
|||||||
|
|
||||||
private void PrintReceipt_OnClick(object sender, RoutedEventArgs e)
|
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 bool _initialLoad = true;
|
||||||
private TotalSaleDetail totalSaleDetail;
|
private TotalSaleDetail totalSaleDetail;
|
||||||
|
private readonly SaleService _saleService = new SaleService();
|
||||||
public SaleOfDay()
|
public SaleOfDay()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
SaleService saleService = new SaleService();
|
totalSaleDetail = _saleService.TotalSale(DateTime.Now);
|
||||||
totalSaleDetail = saleService.TotalSale(DateTime.Now);
|
|
||||||
TotalSale.Content = $"Dagens salg: kr. {totalSaleDetail.TotalSale:0.00}";
|
TotalSale.Content = $"Dagens salg: kr. {totalSaleDetail.TotalSale:0.00}";
|
||||||
StartDatePicker.SelectedDate = DateTime.Now;
|
StartDatePicker.SelectedDate = DateTime.Now;
|
||||||
GridSaleDetail.ItemsSource = totalSaleDetail.TotalSaleCategories;
|
GridSaleDetail.ItemsSource = totalSaleDetail.TotalSaleCategories;
|
||||||
@@ -29,18 +29,20 @@ namespace Pos
|
|||||||
if (_initialLoad)
|
if (_initialLoad)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_initialLoad = false;
|
if (StartDatePicker.SelectedDate is not DateTime selectedDate)
|
||||||
DateTime selectedDate = DateTime.Parse(sender.ToString());
|
return;
|
||||||
SaleService saleService = new SaleService();
|
|
||||||
totalSaleDetail = saleService.TotalSale(selectedDate);
|
totalSaleDetail = _saleService.TotalSale(selectedDate);
|
||||||
TotalSale.Content = $"Dagens salg: kr. {totalSaleDetail.TotalSale:0.00}";
|
TotalSale.Content = $"Dagens salg: kr. {totalSaleDetail.TotalSale:0.00}";
|
||||||
GridSaleDetail.ItemsSource = totalSaleDetail.TotalSaleCategories;
|
GridSaleDetail.ItemsSource = totalSaleDetail.TotalSaleCategories;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PrintSale_OnClick(object sender, RoutedEventArgs e)
|
private void PrintSale_OnClick(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
SaleService saleService = new SaleService();
|
if (StartDatePicker.SelectedDate is not DateTime selectedDate)
|
||||||
saleService.PrintSaleOfDay(StartDatePicker.SelectedDate.Value,totalSaleDetail);
|
return;
|
||||||
|
|
||||||
|
_saleService.PrintSaleOfDay(selectedDate, totalSaleDetail);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ namespace Pos
|
|||||||
private ObservableCollection<ProductGroupEntity> _productGroups;
|
private ObservableCollection<ProductGroupEntity> _productGroups;
|
||||||
private ObservableCollection<SaleGridModel> _saleGridModels;
|
private ObservableCollection<SaleGridModel> _saleGridModels;
|
||||||
private ObservableCollection<AmountGridModel> _paymentGridModels;
|
private ObservableCollection<AmountGridModel> _paymentGridModels;
|
||||||
private List<PriceLineModel> _priceLineModels;
|
private readonly SalesTransactionService _salesTransactionService =
|
||||||
|
new SalesTransactionService();
|
||||||
|
|
||||||
private ProductEntity _selectedProduct;
|
private ProductEntity _selectedProduct;
|
||||||
private ProductGroupEntity _selectedProductGroup;
|
private ProductGroupEntity _selectedProductGroup;
|
||||||
@@ -37,14 +38,10 @@ namespace Pos
|
|||||||
private int _employeeId = -1;
|
private int _employeeId = -1;
|
||||||
private int _productGroupId = -1;
|
private int _productGroupId = -1;
|
||||||
private int _productId = -1;
|
private int _productId = -1;
|
||||||
private int _numberOfProducts = 1;
|
|
||||||
private int _selectedSaleGridIndex = -1;
|
private int _selectedSaleGridIndex = -1;
|
||||||
|
|
||||||
private int _maxCharOfName = 0;
|
private int _maxCharOfName = 0;
|
||||||
|
|
||||||
private decimal _price = 0;
|
|
||||||
private decimal _totalPrice = 0;
|
|
||||||
|
|
||||||
private Button _prevEmpButton = new Button();
|
private Button _prevEmpButton = new Button();
|
||||||
private Button _prevProductGroupButton = new Button();
|
private Button _prevProductGroupButton = new Button();
|
||||||
private Button _prevProduct = new Button();
|
private Button _prevProduct = new Button();
|
||||||
@@ -61,7 +58,6 @@ namespace Pos
|
|||||||
_employees = CacheService.GetEmployee() as ObservableCollection<EmployeeEntity>;
|
_employees = CacheService.GetEmployee() as ObservableCollection<EmployeeEntity>;
|
||||||
_productGroups = CacheService.GetProductGroupsIncludeProducts() as ObservableCollection<ProductGroupEntity>;
|
_productGroups = CacheService.GetProductGroupsIncludeProducts() as ObservableCollection<ProductGroupEntity>;
|
||||||
_saleGridModels = new ObservableCollection<SaleGridModel>();
|
_saleGridModels = new ObservableCollection<SaleGridModel>();
|
||||||
_priceLineModels = new List<PriceLineModel>();
|
|
||||||
_paymentGridModels = new ObservableCollection<AmountGridModel>();
|
_paymentGridModels = new ObservableCollection<AmountGridModel>();
|
||||||
SaleGrid.ItemsSource = _saleGridModels;
|
SaleGrid.ItemsSource = _saleGridModels;
|
||||||
PaymentGrid.ItemsSource = _paymentGridModels;
|
PaymentGrid.ItemsSource = _paymentGridModels;
|
||||||
@@ -150,11 +146,25 @@ namespace Pos
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
decimal price = Convert.ToDecimal(Price.Text);
|
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";
|
NumberOfProducts.Text = "1";
|
||||||
Price.Text = string.Empty;
|
Price.Text = string.Empty;
|
||||||
TotalPrice.Content = "0";
|
TotalPrice.Content = "0";
|
||||||
BuildPrice(false);
|
|
||||||
_productId = -1;
|
_productId = -1;
|
||||||
_productGroupId = -1;
|
_productGroupId = -1;
|
||||||
ManipulateBuySection();
|
ManipulateBuySection();
|
||||||
@@ -162,16 +172,36 @@ namespace Pos
|
|||||||
|
|
||||||
private void Payment_Click(object sender, RoutedEventArgs e)
|
private void Payment_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
//bool isValid = ValidateAmountInput();
|
decimal? amount = null;
|
||||||
//if (!isValid)
|
|
||||||
// return;
|
|
||||||
decimal amount = -1;
|
|
||||||
if (!string.IsNullOrEmpty(Amount.Text))
|
if (!string.IsNullOrEmpty(Amount.Text))
|
||||||
|
{
|
||||||
|
bool isValid = ValidateAmountInput();
|
||||||
|
if (!isValid)
|
||||||
|
return;
|
||||||
|
|
||||||
amount = Convert.ToDecimal(Amount.Text);
|
amount = Convert.ToDecimal(Amount.Text);
|
||||||
|
}
|
||||||
|
|
||||||
Amount.Text = string.Empty;
|
Amount.Text = string.Empty;
|
||||||
Button button = sender as Button;
|
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)
|
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();
|
PriceLineModel priceLineModel;
|
||||||
pModel.NumberProducts = _numberOfProducts;
|
|
||||||
pModel.Price = _price;
|
|
||||||
if (_selectedProduct != null)
|
if (_selectedProduct != null)
|
||||||
{
|
{
|
||||||
pModel.Id = _selectedProduct.Id;
|
priceLineModel = _salesTransactionService.AddSaleLine(
|
||||||
pModel.Name = _selectedProduct.Name;
|
_selectedProduct.Id,
|
||||||
pModel.IsProductGroup = false;
|
_selectedProduct.Name,
|
||||||
|
numberOfProducts,
|
||||||
|
price,
|
||||||
|
false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
pModel.Id = _selectedProductGroup.Id;
|
priceLineModel = _salesTransactionService.AddSaleLine(
|
||||||
pModel.Name = _selectedProductGroup.Name;
|
_selectedProductGroup.Id,
|
||||||
pModel.IsProductGroup = true;
|
_selectedProductGroup.Name,
|
||||||
|
numberOfProducts,
|
||||||
|
price,
|
||||||
|
true);
|
||||||
}
|
}
|
||||||
|
|
||||||
BuildPriceDataGrid(pModel);
|
BuildPriceDataGrid(priceLineModel);
|
||||||
//BuildEmployee();
|
//BuildEmployee();
|
||||||
BuildProductGroup();
|
BuildProductGroup();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CalculateSubPrice()
|
private void CalculateSubPrice()
|
||||||
{
|
{
|
||||||
_price = 0;
|
bool isValid = decimal.TryParse(Price.Text, out decimal price);
|
||||||
bool isValid = decimal.TryParse(Price.Text, out _price);
|
|
||||||
if (!isValid)
|
if (!isValid)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
isValid = Int32.TryParse(NumberOfProducts.Text, out _numberOfProducts);
|
isValid = Int32.TryParse(
|
||||||
|
NumberOfProducts.Text,
|
||||||
|
out int numberOfProducts);
|
||||||
if (!isValid)
|
if (!isValid)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
TotalPrice.Content = _price * _numberOfProducts;
|
try
|
||||||
|
{
|
||||||
|
TotalPrice.Content =
|
||||||
|
_salesTransactionService.CalculateLineTotal(
|
||||||
|
price,
|
||||||
|
numberOfProducts);
|
||||||
|
}
|
||||||
|
catch (ArgumentOutOfRangeException)
|
||||||
|
{
|
||||||
|
TotalPrice.Content = "0";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CalculateTotalLine()
|
private void CalculateTotalLine()
|
||||||
{
|
{
|
||||||
_totalPrice = 0;
|
Total.Content =
|
||||||
foreach (PriceLineModel model in _priceLineModels)
|
$"At betale: Kr. {_salesTransactionService.TotalPrice}";
|
||||||
{
|
|
||||||
_totalPrice += model.Price * model.NumberProducts;
|
|
||||||
}
|
|
||||||
|
|
||||||
Total.Content = $"At betale: Kr. {_totalPrice}";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BuildPriceDataGrid(PriceLineModel priceLineModel)
|
private void BuildPriceDataGrid(PriceLineModel priceLineModel)
|
||||||
{
|
{
|
||||||
_selectedProduct = null;
|
_selectedProduct = null;
|
||||||
_selectedProductGroup = null;
|
_selectedProductGroup = null;
|
||||||
_numberOfProducts = 1;
|
|
||||||
|
|
||||||
_priceLineModels.Add(priceLineModel);
|
|
||||||
CalculateTotalLine();
|
CalculateTotalLine();
|
||||||
|
|
||||||
SaleGridModel model = new SaleGridModel();
|
SaleGridModel model = new SaleGridModel();
|
||||||
@@ -399,38 +437,14 @@ namespace Pos
|
|||||||
TotalPrice.IsEnabled = enabled;
|
TotalPrice.IsEnabled = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BuildAmuntDataGrid(decimal amount, string paymentMethod)
|
private void BuildAmuntDataGrid(
|
||||||
|
decimal? amount,
|
||||||
|
PaymentMethod paymentMethod)
|
||||||
{
|
{
|
||||||
if (amount == -1)
|
AmountGridModel amountGridModel =
|
||||||
{
|
_salesTransactionService.AddPayment(paymentMethod, amount);
|
||||||
amount = 0;
|
|
||||||
foreach (PriceLineModel lineModel in _priceLineModels)
|
|
||||||
{
|
|
||||||
amount += (lineModel.Price * lineModel.NumberProducts);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AmountGridModel amountGridModel = new AmountGridModel();
|
amountGridModel.AmountText = $"Kr. {amountGridModel.Amount}";
|
||||||
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;
|
|
||||||
}
|
|
||||||
_paymentGridModels.Add(amountGridModel);
|
_paymentGridModels.Add(amountGridModel);
|
||||||
|
|
||||||
AmountPayed();
|
AmountPayed();
|
||||||
@@ -438,28 +452,25 @@ namespace Pos
|
|||||||
|
|
||||||
private void AmountPayed()
|
private void AmountPayed()
|
||||||
{
|
{
|
||||||
decimal amountPayed = 0;
|
if (_salesTransactionService.AmountPaid ==
|
||||||
foreach (AmountGridModel paymentGridModel in _paymentGridModels)
|
_salesTransactionService.TotalPrice)
|
||||||
{
|
|
||||||
amountPayed += paymentGridModel.Amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (amountPayed == _totalPrice)
|
|
||||||
{
|
{
|
||||||
btnFinish_Click(null,null);
|
btnFinish_Click(null,null);
|
||||||
return;
|
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;
|
Style style = this.FindResource("BoldLabelWarningNoMargin") as Style;
|
||||||
lblRecived.Style = style;
|
lblRecived.Style = style;
|
||||||
btnFinish.IsEnabled = true;
|
btnFinish.IsEnabled = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
lblRecived.Content = $"At betale: Kr. {_totalPrice - amountPayed}";
|
lblRecived.Content =
|
||||||
|
$"At betale: Kr. {_salesTransactionService.RemainingAmount}";
|
||||||
Style style = this.FindResource("BoldLabelNoMargin") as Style;
|
Style style = this.FindResource("BoldLabelNoMargin") as Style;
|
||||||
lblRecived.Style = style;
|
lblRecived.Style = style;
|
||||||
}
|
}
|
||||||
@@ -473,21 +484,37 @@ namespace Pos
|
|||||||
if (_selectedSaleGridIndex == -1)
|
if (_selectedSaleGridIndex == -1)
|
||||||
return;
|
return;
|
||||||
int index = _selectedSaleGridIndex;
|
int index = _selectedSaleGridIndex;
|
||||||
//_saleGridModels.RemoveAt(_selectedSaleGridIndex);
|
_salesTransactionService.RemoveSaleLineAt(index);
|
||||||
_priceLineModels.RemoveAt(index);
|
_saleGridModels.RemoveAt(index);
|
||||||
_selectedSaleGridIndex = -1;
|
_selectedSaleGridIndex = -1;
|
||||||
CalculateTotalLine();
|
CalculateTotalLine();
|
||||||
|
|
||||||
|
if (_salesTransactionService.AmountPaid > 0)
|
||||||
|
AmountPayed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnFinish_Click(object sender, RoutedEventArgs e)
|
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 saleService = new SaleService();
|
||||||
saleService.Save(_saleGridModels.ToList(),_paymentGridModels.ToList(),_employeeId);
|
saleService.Save(_saleGridModels.ToList(),_paymentGridModels.ToList(),_employeeId);
|
||||||
_paymentGridModels.Clear();
|
_paymentGridModels.Clear();
|
||||||
_priceLineModels.Clear();
|
_salesTransactionService.Reset();
|
||||||
UpdateWindowTitle();
|
UpdateWindowTitle();
|
||||||
_totalPrice = 0;
|
|
||||||
_saleGridModels.Clear();
|
_saleGridModels.Clear();
|
||||||
lblRecived.Content = string.Empty;
|
lblRecived.Content = string.Empty;
|
||||||
_selectedSaleGridIndex = -1;
|
_selectedSaleGridIndex = -1;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ using System.Windows.Input;
|
|||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
using System.Windows.Shapes;
|
using System.Windows.Shapes;
|
||||||
using Database.Repository;
|
using Pos.Service;
|
||||||
|
|
||||||
namespace Pos.Setting
|
namespace Pos.Setting
|
||||||
{
|
{
|
||||||
@@ -20,6 +20,9 @@ namespace Pos.Setting
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class AddEmployeeWindow : Window
|
public partial class AddEmployeeWindow : Window
|
||||||
{
|
{
|
||||||
|
private readonly EmployeeService _employeeService =
|
||||||
|
new EmployeeService();
|
||||||
|
|
||||||
public AddEmployeeWindow()
|
public AddEmployeeWindow()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
@@ -27,9 +30,19 @@ namespace Pos.Setting
|
|||||||
|
|
||||||
private void btnAdd_Click(object sender, RoutedEventArgs e)
|
private void btnAdd_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
EmployeeRepository employeeRepository = new EmployeeRepository();
|
try
|
||||||
employeeRepository.Add(txtStaff.Text);
|
{
|
||||||
this.Close();
|
_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;
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
using System.Windows.Shapes;
|
using System.Windows.Shapes;
|
||||||
using Database.Repository;
|
using Pos.Service;
|
||||||
|
|
||||||
namespace Pos.Setting
|
namespace Pos.Setting
|
||||||
{
|
{
|
||||||
@@ -21,6 +21,8 @@ namespace Pos.Setting
|
|||||||
public partial class DeleteEmployeeWindow : Window
|
public partial class DeleteEmployeeWindow : Window
|
||||||
{
|
{
|
||||||
private int _employeeId;
|
private int _employeeId;
|
||||||
|
private readonly EmployeeService _employeeService =
|
||||||
|
new EmployeeService();
|
||||||
|
|
||||||
public DeleteEmployeeWindow(int employeeId, string staffName)
|
public DeleteEmployeeWindow(int employeeId, string staffName)
|
||||||
{
|
{
|
||||||
@@ -31,9 +33,8 @@ namespace Pos.Setting
|
|||||||
|
|
||||||
private void btnYes_Click(object sender, RoutedEventArgs e)
|
private void btnYes_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
EmployeeRepository employeeRepository = new EmployeeRepository();
|
_employeeService.ArchiveEmployee(_employeeId);
|
||||||
employeeRepository.Delete(_employeeId);
|
Close();
|
||||||
this.Close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnNo_Click(object sender, RoutedEventArgs e)
|
private void btnNo_Click(object sender, RoutedEventArgs e)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ using System.Windows.Input;
|
|||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
using System.Windows.Shapes;
|
using System.Windows.Shapes;
|
||||||
using Database.Repository;
|
using Pos.Service;
|
||||||
|
|
||||||
namespace Pos.Setting
|
namespace Pos.Setting
|
||||||
{
|
{
|
||||||
@@ -21,6 +21,8 @@ namespace Pos.Setting
|
|||||||
public partial class EditEmployeeWindow : Window
|
public partial class EditEmployeeWindow : Window
|
||||||
{
|
{
|
||||||
private int _employeeId;
|
private int _employeeId;
|
||||||
|
private readonly EmployeeService _employeeService =
|
||||||
|
new EmployeeService();
|
||||||
|
|
||||||
public EditEmployeeWindow(int employeeId, string staffName)
|
public EditEmployeeWindow(int employeeId, string staffName)
|
||||||
{
|
{
|
||||||
@@ -31,9 +33,21 @@ namespace Pos.Setting
|
|||||||
|
|
||||||
private void btnEdit_Click(object sender, RoutedEventArgs e)
|
private void btnEdit_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
EmployeeRepository employeeRepository = new EmployeeRepository();
|
try
|
||||||
employeeRepository.Edit(_employeeId,txtEmployee.Text);
|
{
|
||||||
this.Close();
|
_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.Media.Imaging;
|
||||||
using System.Windows.Shapes;
|
using System.Windows.Shapes;
|
||||||
using Database.Models;
|
using Database.Models;
|
||||||
using Database.Repository;
|
using Pos.Service;
|
||||||
|
|
||||||
namespace Pos.Setting
|
namespace Pos.Setting
|
||||||
{
|
{
|
||||||
@@ -24,6 +24,8 @@ namespace Pos.Setting
|
|||||||
{
|
{
|
||||||
private int _selectedEmployee = -1;
|
private int _selectedEmployee = -1;
|
||||||
private string _employeeName = string.Empty;
|
private string _employeeName = string.Empty;
|
||||||
|
private readonly EmployeeService _employeeService =
|
||||||
|
new EmployeeService();
|
||||||
public EmployeeWindow()
|
public EmployeeWindow()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
@@ -62,15 +64,14 @@ namespace Pos.Setting
|
|||||||
deleteEmployeeWindow.Show();
|
deleteEmployeeWindow.Show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WindowClosed(object? sender, EventArgs e)
|
private void WindowClosed(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
LoadData();
|
LoadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadData()
|
private void LoadData()
|
||||||
{
|
{
|
||||||
using EmployeeRepository employeeRepository = new EmployeeRepository();
|
List<EmployeeEntity> allStaff = _employeeService.GetEmployees();
|
||||||
List<EmployeeEntity> allStaff = employeeRepository.GetAll();
|
|
||||||
LstStaff.Items.Clear();
|
LstStaff.Items.Clear();
|
||||||
foreach (EmployeeEntity staff in allStaff)
|
foreach (EmployeeEntity staff in allStaff)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
using Database.Repository;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
|
using Pos.Service;
|
||||||
|
|
||||||
namespace Pos.Setting.ProductGroup
|
namespace Pos.Setting.ProductGroup
|
||||||
{
|
{
|
||||||
@@ -11,6 +11,11 @@ namespace Pos.Setting.ProductGroup
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class AddProductWindow : Window
|
public partial class AddProductWindow : Window
|
||||||
{
|
{
|
||||||
|
private readonly ProductGroupService _productGroupService =
|
||||||
|
new ProductGroupService();
|
||||||
|
private readonly ProductService _productService =
|
||||||
|
new ProductService();
|
||||||
|
|
||||||
public AddProductWindow()
|
public AddProductWindow()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
@@ -19,13 +24,16 @@ namespace Pos.Setting.ProductGroup
|
|||||||
|
|
||||||
private void LoadData(int selectedIndex)
|
private void LoadData(int selectedIndex)
|
||||||
{
|
{
|
||||||
ProductGroupRepository productGroupRepository = new ProductGroupRepository();
|
List<Database.Models.ProductGroupEntity> productGroups =
|
||||||
List<Database.Models.ProductGroupEntity> products = productGroupRepository.GetAll();
|
_productGroupService.GetProductGroups();
|
||||||
foreach (Database.Models.ProductGroupEntity productGroup in products)
|
|
||||||
|
foreach (Database.Models.ProductGroupEntity productGroup in productGroups)
|
||||||
{
|
{
|
||||||
ComboBoxItem comboBoxItem = new ComboBoxItem();
|
ComboBoxItem comboBoxItem = new ComboBoxItem
|
||||||
comboBoxItem.Tag = productGroup.Id;
|
{
|
||||||
comboBoxItem.Content = productGroup.Name;
|
Tag = productGroup.Id,
|
||||||
|
Content = productGroup.Name
|
||||||
|
};
|
||||||
cmbProductGroup.Items.Add(comboBoxItem);
|
cmbProductGroup.Items.Add(comboBoxItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,12 +42,23 @@ namespace Pos.Setting.ProductGroup
|
|||||||
|
|
||||||
private void btnAdd_Click(object sender, RoutedEventArgs e)
|
private void btnAdd_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
ComboBoxItem item = (ComboBoxItem) cmbProductGroup.SelectedItem;
|
ComboBoxItem item = (ComboBoxItem)cmbProductGroup.SelectedItem;
|
||||||
string name = txtProduct.Text;
|
|
||||||
int id = Convert.ToInt32(item.Tag);
|
try
|
||||||
ProductRepository productRepository = new ProductRepository();
|
{
|
||||||
productRepository.Add(name,id);
|
_productService.AddProduct(
|
||||||
this.Close();
|
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;
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
using System.Windows.Shapes;
|
using System.Windows.Shapes;
|
||||||
using Database.Repository;
|
using Pos.Service;
|
||||||
|
|
||||||
namespace Pos.Setting.Product
|
namespace Pos.Setting.Product
|
||||||
{
|
{
|
||||||
@@ -21,20 +21,21 @@ namespace Pos.Setting.Product
|
|||||||
public partial class ArchiveProductWindow : Window
|
public partial class ArchiveProductWindow : Window
|
||||||
{
|
{
|
||||||
private int _productId;
|
private int _productId;
|
||||||
|
private readonly ProductService _productService =
|
||||||
|
new ProductService();
|
||||||
public ArchiveProductWindow(int productId)
|
public ArchiveProductWindow(int productId)
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
_productId = productId;
|
_productId = productId;
|
||||||
ProductRepository productRepository = new ProductRepository();
|
Database.Models.ProductEntity product =
|
||||||
Database.Models.ProductEntity product = productRepository.GetById(_productId);
|
_productService.GetProduct(_productId);
|
||||||
txtDelProduct.Text = product.Name;
|
txtDelProduct.Text = product.Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnYes_Click(object sender, RoutedEventArgs e)
|
private void btnYes_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
ProductRepository productRepository = new ProductRepository();
|
_productService.ArchiveProduct(_productId);
|
||||||
productRepository.Archive(_productId);
|
Close();
|
||||||
this.Close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnNo_Click(object sender, RoutedEventArgs e)
|
private void btnNo_Click(object sender, RoutedEventArgs e)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ using System.Windows.Input;
|
|||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
using System.Windows.Shapes;
|
using System.Windows.Shapes;
|
||||||
using Database.Repository;
|
using Pos.Service;
|
||||||
|
|
||||||
namespace Pos.Setting.Product
|
namespace Pos.Setting.Product
|
||||||
{
|
{
|
||||||
@@ -23,6 +23,8 @@ namespace Pos.Setting.Product
|
|||||||
{
|
{
|
||||||
ObservableCollection<Database.Models.ProductEntity> _productList = new ObservableCollection<Database.Models.ProductEntity>();
|
ObservableCollection<Database.Models.ProductEntity> _productList = new ObservableCollection<Database.Models.ProductEntity>();
|
||||||
private int _productGroupId;
|
private int _productGroupId;
|
||||||
|
private readonly ProductService _productService =
|
||||||
|
new ProductService();
|
||||||
public ChangeProductOrderWindow(int productGroupId)
|
public ChangeProductOrderWindow(int productGroupId)
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
@@ -32,8 +34,8 @@ namespace Pos.Setting.Product
|
|||||||
|
|
||||||
private void LoadData()
|
private void LoadData()
|
||||||
{
|
{
|
||||||
ProductRepository productRepository = new ProductRepository();
|
List<Database.Models.ProductEntity> products =
|
||||||
List<Database.Models.ProductEntity> products = productRepository.GetByProductGroup(_productGroupId);
|
_productService.GetProducts(_productGroupId);
|
||||||
foreach (Database.Models.ProductEntity product in products)
|
foreach (Database.Models.ProductEntity product in products)
|
||||||
{
|
{
|
||||||
_productList.Add(product);
|
_productList.Add(product);
|
||||||
@@ -86,13 +88,10 @@ namespace Pos.Setting.Product
|
|||||||
|
|
||||||
private void btnSave_Click(object sender, RoutedEventArgs e)
|
private void btnSave_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
ProductRepository productRepository = new ProductRepository();
|
_productService.UpdateOrder(
|
||||||
for (int i = 0; i < _productList.Count; i++)
|
_productGroupId,
|
||||||
{
|
_productList.Select(product => product.Id).ToList());
|
||||||
Database.Models.ProductEntity product = _productList[i];
|
Close();
|
||||||
productRepository.SetIndex(product.Id, i);
|
|
||||||
}
|
|
||||||
this.Close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ using System.Windows.Input;
|
|||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
using System.Windows.Shapes;
|
using System.Windows.Shapes;
|
||||||
using Database.Repository;
|
using Pos.Service;
|
||||||
|
|
||||||
namespace Pos.Setting.Product
|
namespace Pos.Setting.Product
|
||||||
{
|
{
|
||||||
@@ -23,6 +23,10 @@ namespace Pos.Setting.Product
|
|||||||
private int _productId;
|
private int _productId;
|
||||||
private int _selectedProductGroupId;
|
private int _selectedProductGroupId;
|
||||||
private Database.Models.ProductEntity _product;
|
private Database.Models.ProductEntity _product;
|
||||||
|
private readonly ProductGroupService _productGroupService =
|
||||||
|
new ProductGroupService();
|
||||||
|
private readonly ProductService _productService =
|
||||||
|
new ProductService();
|
||||||
|
|
||||||
public EditProductWindow(int productId, int selectedProductGroupId)
|
public EditProductWindow(int productId, int selectedProductGroupId)
|
||||||
{
|
{
|
||||||
@@ -34,8 +38,8 @@ namespace Pos.Setting.Product
|
|||||||
|
|
||||||
private void Init()
|
private void Init()
|
||||||
{
|
{
|
||||||
ProductGroupRepository categoryRepository = new ProductGroupRepository();
|
List<Database.Models.ProductGroupEntity> productGroups =
|
||||||
List<Database.Models.ProductGroupEntity> productGroups = categoryRepository.GetAll();
|
_productGroupService.GetProductGroups();
|
||||||
foreach (Database.Models.ProductGroupEntity productGroup in productGroups)
|
foreach (Database.Models.ProductGroupEntity productGroup in productGroups)
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -46,17 +50,29 @@ namespace Pos.Setting.Product
|
|||||||
}
|
}
|
||||||
cmbProductGroup.SelectedIndex = _selectedProductGroupId;
|
cmbProductGroup.SelectedIndex = _selectedProductGroupId;
|
||||||
|
|
||||||
ProductRepository productRepository = new ProductRepository();
|
_product = _productService.GetProduct(_productId);
|
||||||
_product = productRepository.GetById(_productId);
|
|
||||||
txtProduct.Text = _product.Name;
|
txtProduct.Text = _product.Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnEdit_Click(object sender, RoutedEventArgs e)
|
private void btnEdit_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
ComboBoxItem item = (ComboBoxItem)cmbProductGroup.SelectedItem;
|
ComboBoxItem item = (ComboBoxItem)cmbProductGroup.SelectedItem;
|
||||||
ProductRepository productRepository = new ProductRepository();
|
try
|
||||||
productRepository.Update(_product.Id,(int)item.Tag,txtProduct.Text);
|
{
|
||||||
this.Close();
|
_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.Collections.Generic;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using Database.Repository;
|
using Pos.Service;
|
||||||
using Pos.Setting.Product;
|
using Pos.Setting.Product;
|
||||||
|
|
||||||
namespace Pos.Setting.ProductGroup
|
namespace Pos.Setting.ProductGroup
|
||||||
@@ -14,7 +14,10 @@ namespace Pos.Setting.ProductGroup
|
|||||||
public partial class ProductWindow : Window
|
public partial class ProductWindow : Window
|
||||||
{
|
{
|
||||||
private List<Database.Models.ProductGroupEntity> _productGroups;
|
private List<Database.Models.ProductGroupEntity> _productGroups;
|
||||||
private int _productId;
|
private readonly ProductGroupService _productGroupService =
|
||||||
|
new ProductGroupService();
|
||||||
|
private readonly ProductService _productService =
|
||||||
|
new ProductService();
|
||||||
|
|
||||||
public ProductWindow()
|
public ProductWindow()
|
||||||
{
|
{
|
||||||
@@ -26,8 +29,7 @@ namespace Pos.Setting.ProductGroup
|
|||||||
|
|
||||||
private void Init()
|
private void Init()
|
||||||
{
|
{
|
||||||
ProductGroupRepository categoryRepository = new ProductGroupRepository();
|
_productGroups = _productGroupService.GetProductGroups();
|
||||||
_productGroups = categoryRepository.GetAll();
|
|
||||||
cmbCat.Items.Clear();
|
cmbCat.Items.Clear();
|
||||||
foreach (Database.Models.ProductGroupEntity productGroup in _productGroups)
|
foreach (Database.Models.ProductGroupEntity productGroup in _productGroups)
|
||||||
{
|
{
|
||||||
@@ -38,14 +40,19 @@ namespace Pos.Setting.ProductGroup
|
|||||||
cmbCat.Items.Add(comboBoxItem);
|
cmbCat.Items.Add(comboBoxItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
cmbCat.SelectedIndex = 0;
|
if (_productGroups.Count > 0)
|
||||||
|
cmbCat.SelectedIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadData()
|
private void LoadData()
|
||||||
{
|
{
|
||||||
|
|
||||||
ProductRepository productRepository = new ProductRepository();
|
if (cmbCat.SelectedIndex < 0)
|
||||||
List<Database.Models.ProductEntity> products = productRepository.GetByProductGroup(_productGroups[cmbCat.SelectedIndex].Id);
|
return;
|
||||||
|
|
||||||
|
List<Database.Models.ProductEntity> products =
|
||||||
|
_productService.GetProducts(
|
||||||
|
_productGroups[cmbCat.SelectedIndex].Id);
|
||||||
lstProductGroup.Items.Clear();
|
lstProductGroup.Items.Clear();
|
||||||
foreach (Database.Models.ProductEntity product in products)
|
foreach (Database.Models.ProductEntity product in products)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ using System.Windows.Input;
|
|||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
using System.Windows.Shapes;
|
using System.Windows.Shapes;
|
||||||
using Database.Repository;
|
using Pos.Service;
|
||||||
|
|
||||||
namespace Pos.Setting.Category
|
namespace Pos.Setting.Category
|
||||||
{
|
{
|
||||||
@@ -20,6 +20,9 @@ namespace Pos.Setting.Category
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class AddProductGroupWindow : Window
|
public partial class AddProductGroupWindow : Window
|
||||||
{
|
{
|
||||||
|
private readonly ProductGroupService _productGroupService =
|
||||||
|
new ProductGroupService();
|
||||||
|
|
||||||
public AddProductGroupWindow()
|
public AddProductGroupWindow()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
@@ -27,9 +30,19 @@ namespace Pos.Setting.Category
|
|||||||
|
|
||||||
private void btnAdd_Click(object sender, RoutedEventArgs e)
|
private void btnAdd_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
ProductGroupRepository categoryRepository = new ProductGroupRepository();
|
try
|
||||||
categoryRepository.Add(txtProductGroup.Text);
|
{
|
||||||
this.Close();
|
_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;
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
using System.Windows.Shapes;
|
using System.Windows.Shapes;
|
||||||
using Database.Repository;
|
using Pos.Service;
|
||||||
|
|
||||||
namespace Pos.Setting.Category
|
namespace Pos.Setting.Category
|
||||||
{
|
{
|
||||||
@@ -21,6 +21,8 @@ namespace Pos.Setting.Category
|
|||||||
public partial class ArchiveProductGroupWindow : Window
|
public partial class ArchiveProductGroupWindow : Window
|
||||||
{
|
{
|
||||||
private readonly int _id;
|
private readonly int _id;
|
||||||
|
private readonly ProductGroupService _productGroupService =
|
||||||
|
new ProductGroupService();
|
||||||
public ArchiveProductGroupWindow(string name, int id)
|
public ArchiveProductGroupWindow(string name, int id)
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
@@ -36,9 +38,8 @@ namespace Pos.Setting.Category
|
|||||||
|
|
||||||
private void btnYes_Click(object sender, RoutedEventArgs e)
|
private void btnYes_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
ProductGroupRepository categoryRepository = new ProductGroupRepository();
|
_productGroupService.ArchiveProductGroup(_id);
|
||||||
categoryRepository.Archive(_id);
|
Close();
|
||||||
this.Close();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ using System.Windows.Input;
|
|||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
using System.Windows.Shapes;
|
using System.Windows.Shapes;
|
||||||
using Database.Repository;
|
using Pos.Service;
|
||||||
|
|
||||||
namespace Pos.Setting.Category
|
namespace Pos.Setting.Category
|
||||||
{
|
{
|
||||||
@@ -22,6 +22,8 @@ namespace Pos.Setting.Category
|
|||||||
public partial class ChangeProductGroupOrderWindow : Window
|
public partial class ChangeProductGroupOrderWindow : Window
|
||||||
{
|
{
|
||||||
ObservableCollection<Database.Models.ProductGroupEntity> _prodGroupList = new ObservableCollection<Database.Models.ProductGroupEntity>();
|
ObservableCollection<Database.Models.ProductGroupEntity> _prodGroupList = new ObservableCollection<Database.Models.ProductGroupEntity>();
|
||||||
|
private readonly ProductGroupService _productGroupService =
|
||||||
|
new ProductGroupService();
|
||||||
public ChangeProductGroupOrderWindow()
|
public ChangeProductGroupOrderWindow()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
@@ -30,8 +32,8 @@ namespace Pos.Setting.Category
|
|||||||
|
|
||||||
private void LoadData()
|
private void LoadData()
|
||||||
{
|
{
|
||||||
ProductGroupRepository categoryRepository = new ProductGroupRepository();
|
List<Database.Models.ProductGroupEntity> categories =
|
||||||
List<Database.Models.ProductGroupEntity> categories = categoryRepository.GetAll();
|
_productGroupService.GetProductGroups();
|
||||||
foreach (Database.Models.ProductGroupEntity category in categories)
|
foreach (Database.Models.ProductGroupEntity category in categories)
|
||||||
{
|
{
|
||||||
_prodGroupList.Add(category);
|
_prodGroupList.Add(category);
|
||||||
@@ -84,13 +86,9 @@ namespace Pos.Setting.Category
|
|||||||
|
|
||||||
private void btnSave_Click(object sender, RoutedEventArgs e)
|
private void btnSave_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
ProductGroupRepository categoryRepository = new ProductGroupRepository();
|
_productGroupService.UpdateOrder(
|
||||||
for (int i = 0; i < _prodGroupList.Count; i++)
|
_prodGroupList.Select(productGroup => productGroup.Id).ToList());
|
||||||
{
|
Close();
|
||||||
Database.Models.ProductGroupEntity productGroup = _prodGroupList[i];
|
|
||||||
categoryRepository.SetIndex(productGroup.Id,i);
|
|
||||||
}
|
|
||||||
this.Close();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ using System.Windows.Input;
|
|||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
using System.Windows.Shapes;
|
using System.Windows.Shapes;
|
||||||
using Database.Repository;
|
using Pos.Service;
|
||||||
|
|
||||||
namespace Pos.Setting.Category
|
namespace Pos.Setting.Category
|
||||||
{
|
{
|
||||||
@@ -21,6 +21,8 @@ namespace Pos.Setting.Category
|
|||||||
public partial class EditProductGroupWindow : Window
|
public partial class EditProductGroupWindow : Window
|
||||||
{
|
{
|
||||||
private readonly int _id;
|
private readonly int _id;
|
||||||
|
private readonly ProductGroupService _productGroupService =
|
||||||
|
new ProductGroupService();
|
||||||
|
|
||||||
public EditProductGroupWindow(string name, int id)
|
public EditProductGroupWindow(string name, int id)
|
||||||
{
|
{
|
||||||
@@ -31,9 +33,21 @@ namespace Pos.Setting.Category
|
|||||||
|
|
||||||
private void btnEdit_Click(object sender, RoutedEventArgs e)
|
private void btnEdit_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
ProductGroupRepository categoryRepository = new ProductGroupRepository();
|
try
|
||||||
categoryRepository.Edit(txtProductGroup.Text,_id);
|
{
|
||||||
this.Close();
|
_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;
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
using System.Windows.Shapes;
|
using System.Windows.Shapes;
|
||||||
using Database.Repository;
|
using Pos.Service;
|
||||||
using Pos.Setting.Category;
|
using Pos.Setting.Category;
|
||||||
|
|
||||||
namespace Pos.Category
|
namespace Pos.Category
|
||||||
@@ -23,6 +23,8 @@ namespace Pos.Category
|
|||||||
{
|
{
|
||||||
private int _id = -1;
|
private int _id = -1;
|
||||||
private string _name = string.Empty;
|
private string _name = string.Empty;
|
||||||
|
private readonly ProductGroupService _productGroupService =
|
||||||
|
new ProductGroupService();
|
||||||
|
|
||||||
public ProductGroupWindow()
|
public ProductGroupWindow()
|
||||||
{
|
{
|
||||||
@@ -53,8 +55,8 @@ namespace Pos.Category
|
|||||||
|
|
||||||
private void LoadData()
|
private void LoadData()
|
||||||
{
|
{
|
||||||
ProductGroupRepository categoryRepository = new ProductGroupRepository();
|
List<Database.Models.ProductGroupEntity> categories =
|
||||||
List<Database.Models.ProductGroupEntity> categories = categoryRepository.GetAll();
|
_productGroupService.GetProductGroups();
|
||||||
LstCategory.Items.Clear();
|
LstCategory.Items.Clear();
|
||||||
foreach (Database.Models.ProductGroupEntity category in categories)
|
foreach (Database.Models.ProductGroupEntity category in categories)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ using System.Windows.Input;
|
|||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
using System.Windows.Shapes;
|
using System.Windows.Shapes;
|
||||||
using Database.Repository;
|
|
||||||
using Pos.Category;
|
using Pos.Category;
|
||||||
|
using Pos.Service;
|
||||||
using Pos.Setting.ProductGroup;
|
using Pos.Setting.ProductGroup;
|
||||||
|
|
||||||
namespace Pos.Setting
|
namespace Pos.Setting
|
||||||
@@ -22,6 +22,9 @@ namespace Pos.Setting
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class SettingWindow : Window
|
public partial class SettingWindow : Window
|
||||||
{
|
{
|
||||||
|
private readonly ProductGroupService _productGroupService =
|
||||||
|
new ProductGroupService();
|
||||||
|
|
||||||
public SettingWindow()
|
public SettingWindow()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
@@ -42,10 +45,8 @@ namespace Pos.Setting
|
|||||||
|
|
||||||
private void btnProductGroups_Click(object sender, RoutedEventArgs e)
|
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.
|
//Check if there is any categories, if not close this window with a message.
|
||||||
ProductGroupRepository categoryRepository = new ProductGroupRepository();
|
bool anyExist = _productGroupService.HasProductGroups();
|
||||||
bool anyExist = categoryRepository.Any();
|
|
||||||
if (!anyExist)
|
if (!anyExist)
|
||||||
{
|
{
|
||||||
MessageBox.Show("Der er ingen kategorier, opret dem først", "Kategorier", MessageBoxButton.OK,
|
MessageBox.Show("Der er ingen kategorier, opret dem først", "Kategorier", MessageBoxButton.OK,
|
||||||
@@ -53,6 +54,7 @@ namespace Pos.Setting
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
ProductWindow productWindow = new ProductWindow();
|
||||||
productWindow.Show();
|
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
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
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
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
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
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
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" onclick="@(() => Nav.NavigateTo("/todaySales"))">
|
||||||
|
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>
|
||||||
23
PointOfSale/Pos.Web/Components/Pages/Home.razor.css
Normal file
23
PointOfSale/Pos.Web/Components/Pages/Home.razor.css
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 680px) {
|
||||||
|
.home-container {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
margin: 24px 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
5
PointOfSale/Pos.Web/Components/Pages/NotFound.razor
Normal file
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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
@using Database.Models
|
||||||
|
@using Pos.Service
|
||||||
|
@inject ProductGroupService ProductGroupService
|
||||||
|
|
||||||
|
@if (IsOpen)
|
||||||
|
{
|
||||||
|
<div class="dialog-overlay">
|
||||||
|
<div class="product-dialog" role="dialog" aria-modal="true" aria-labelledby="product-dialog-title">
|
||||||
|
<div class="dialog-header">
|
||||||
|
<h2 id="product-dialog-title">@(_productGroupId.HasValue ? "Redigér vare" : "Opret vare")</h2>
|
||||||
|
<button class="close-button" type="button" aria-label="Luk" @onclick="CloseAsync">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="dialog-content">
|
||||||
|
<label for="product-name">Navn</label>
|
||||||
|
<input id="product-name" class="dialog-input" type="text" placeholder="Indtast navn"
|
||||||
|
@bind="_name" @bind:event="oninput" />
|
||||||
|
|
||||||
|
<label for="product-vat">Moms i procent</label>
|
||||||
|
<input id="product-vat" class="dialog-input" type="number" min="0" max="100" step="0.01"
|
||||||
|
@bind="_vatRate" />
|
||||||
|
</div>
|
||||||
|
<div class="dialog-actions">
|
||||||
|
<button class="cancel-button" type="button" @onclick="CloseAsync">Annuller</button>
|
||||||
|
<button class="save-button" type="button" disabled="@(!CanSave)" @onclick="SaveAsync">
|
||||||
|
@(_productGroupId.HasValue ? "Gem ændringer" : "Opret vare")
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
using Database.Models;
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
|
namespace Pos.Web.Components.Pages.Settings
|
||||||
|
{
|
||||||
|
public partial class ProductGroupDialog
|
||||||
|
{
|
||||||
|
[Parameter] public bool IsOpen { get; set; }
|
||||||
|
[Parameter] public EventCallback<bool> IsOpenChanged { get; set; }
|
||||||
|
[Parameter] public ProductGroupEntity? ProductGroup { get; set; }
|
||||||
|
[Parameter] public EventCallback ProductGroupSaved { get; set; }
|
||||||
|
|
||||||
|
private int? _productGroupId;
|
||||||
|
private string _name = string.Empty;
|
||||||
|
private decimal _vatRate = 25m;
|
||||||
|
private bool _wasOpen;
|
||||||
|
private bool CanSave => !string.IsNullOrWhiteSpace(_name) && _vatRate >= 0 && _vatRate <= 100;
|
||||||
|
|
||||||
|
protected override void OnParametersSet()
|
||||||
|
{
|
||||||
|
if (IsOpen && !_wasOpen)
|
||||||
|
{
|
||||||
|
_productGroupId = ProductGroup?.Id;
|
||||||
|
_name = ProductGroup?.Name ?? string.Empty;
|
||||||
|
_vatRate = ProductGroup?.VatRate ?? 25m;
|
||||||
|
}
|
||||||
|
_wasOpen = IsOpen;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SaveAsync()
|
||||||
|
{
|
||||||
|
if (!CanSave)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_productGroupId.HasValue)
|
||||||
|
ProductGroupService.UpdateProductGroup(_productGroupId.Value, _name, _vatRate);
|
||||||
|
else
|
||||||
|
ProductGroupService.AddProductGroup(_name, _vatRate);
|
||||||
|
|
||||||
|
await ProductGroupSaved.InvokeAsync();
|
||||||
|
await CloseAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CloseAsync()
|
||||||
|
{
|
||||||
|
_wasOpen = false;
|
||||||
|
await IsOpenChanged.InvokeAsync(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
.dialog-overlay { position: fixed; inset: 0; z-index: 1000; display: grid; place-items: center; padding: 24px; background: rgb(15 23 42 / 45%); }
|
||||||
|
.product-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 { margin-top: 8px; font-size: 16px; font-weight: 500; }
|
||||||
|
.dialog-input { box-sizing: border-box; width: 100%; height: 48px; padding: 0 14px; border: 1px solid #8a8a8a; border-radius: 6px; outline: none; background: #fff; color: #242424; font: inherit; }
|
||||||
|
.dialog-input:focus { border-color: #0f6cbd; box-shadow: 0 0 0 1px #0f6cbd; }
|
||||||
|
.dialog-actions { display: flex; justify-content: flex-end; gap: 16px; padding: 24px 40px; border-top: 1px solid #e2e8f0; }
|
||||||
|
.cancel-button, .save-button { min-height: 42px; padding: 0 22px; border-radius: 6px; cursor: pointer; font: inherit; font-weight: 500; }
|
||||||
|
.cancel-button { border: 1px solid #616161; background: #fff; color: #242424; }
|
||||||
|
.save-button { border: 1px solid #0f6cbd; background: #0f6cbd; color: #fff; }
|
||||||
|
.save-button:disabled { border-color: #d1d1d1; background: #e0e0e0; color: #707070; cursor: not-allowed; }
|
||||||
77
PointOfSale/Pos.Web/Components/Pages/Settings/Products.razor
Normal file
77
PointOfSale/Pos.Web/Components/Pages/Settings/Products.razor
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
@page "/settings/products"
|
||||||
|
@rendermode InteractiveServer
|
||||||
|
@using Database.Models
|
||||||
|
@using Pos.Service
|
||||||
|
@inject IDialogService DialogService
|
||||||
|
@inject ProductGroupService ProductGroupService
|
||||||
|
|
||||||
|
<Header />
|
||||||
|
|
||||||
|
<div class="products-page">
|
||||||
|
<div class="products-header">
|
||||||
|
<h1>Vare</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="products-toolbar">
|
||||||
|
<label class="search-field">
|
||||||
|
<span aria-hidden="true">⌕</span>
|
||||||
|
<input type="search" placeholder="Søg vare" aria-label="Søg vare"
|
||||||
|
@bind="_searchText" @bind:event="oninput" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<FluentButton Class="create-button" Appearance="@ButtonAppearance.Primary"
|
||||||
|
OnClick="OpenCreateDialog">
|
||||||
|
<span class="button-icon" aria-hidden="true">+</span> Opret vare
|
||||||
|
</FluentButton>
|
||||||
|
|
||||||
|
<FluentButton Class="save-order-button" Appearance="@ButtonAppearance.Outline"
|
||||||
|
Disabled="@(!_orderHasChanged)" OnClick="SaveOrder">
|
||||||
|
<span class="button-icon" aria-hidden="true">⇅</span> Gem rækkefølge
|
||||||
|
</FluentButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (_isLoading)
|
||||||
|
{
|
||||||
|
<FluentSpinner />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="products-table">
|
||||||
|
<div class="products-row products-column-header">
|
||||||
|
<span>Rækkefølge</span><span>Navn</span><span>Moms</span><span>Status</span><span>Handling</span>
|
||||||
|
</div>
|
||||||
|
<div class="products-list">
|
||||||
|
@foreach (ProductGroupEntity productGroup in FilteredProductGroups)
|
||||||
|
{
|
||||||
|
<div class="products-row product-row @GetDragClass(productGroup)" @key="productGroup.Id"
|
||||||
|
draggable="true" @ondragstart="@(() => StartDragging(productGroup))"
|
||||||
|
@ondragover:preventDefault="true" @ondrop="@(() => DropProductGroup(productGroup))"
|
||||||
|
@ondragend="StopDragging">
|
||||||
|
<span class="drag-handle" title="Træk for at ændre rækkefølge">⠿</span>
|
||||||
|
<span class="product-name">@productGroup.Name</span>
|
||||||
|
<span>@productGroup.VatRate.ToString("0.##") %</span>
|
||||||
|
<span class="active-status">Aktiv</span>
|
||||||
|
<span class="row-actions">
|
||||||
|
<FluentButton Appearance="@ButtonAppearance.Outline"
|
||||||
|
OnClick="@(() => OpenEditDialog(productGroup))">Redigér</FluentButton>
|
||||||
|
<FluentButton Class="delete-button" Appearance="@ButtonAppearance.Outline"
|
||||||
|
OnClick="@(() => ConfirmDeactivation(productGroup))">Slet</FluentButton>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@if (!FilteredProductGroups.Any())
|
||||||
|
{
|
||||||
|
<p class="empty-state">Ingen varer matcher din søgning.</p>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<p class="products-help"><span aria-hidden="true">ⓘ</span>
|
||||||
|
Træk i håndtaget for at ændre rækkefølgen. Deaktiverede varer vises ikke i kassesalget.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ProductGroupDialog @bind-IsOpen="_showProductGroupDialog"
|
||||||
|
ProductGroup="_productGroupToEdit"
|
||||||
|
ProductGroupSaved="ProductGroupSaved" />
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
using Database.Models;
|
||||||
|
using Microsoft.FluentUI.AspNetCore.Components;
|
||||||
|
|
||||||
|
namespace Pos.Web.Components.Pages.Settings
|
||||||
|
{
|
||||||
|
public partial class Products
|
||||||
|
{
|
||||||
|
private List<ProductGroupEntity> _productGroups = [];
|
||||||
|
private ProductGroupEntity? _draggedProductGroup;
|
||||||
|
private ProductGroupEntity? _productGroupToEdit;
|
||||||
|
private string _searchText = string.Empty;
|
||||||
|
private bool _isLoading = true;
|
||||||
|
private bool _showProductGroupDialog;
|
||||||
|
private bool _orderHasChanged;
|
||||||
|
|
||||||
|
private IEnumerable<ProductGroupEntity> FilteredProductGroups =>
|
||||||
|
_productGroups.Where(productGroup =>
|
||||||
|
string.IsNullOrWhiteSpace(_searchText) ||
|
||||||
|
productGroup.Name.Contains(_searchText, StringComparison.CurrentCultureIgnoreCase));
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
LoadProductGroups();
|
||||||
|
_isLoading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadProductGroups() =>
|
||||||
|
_productGroups = ProductGroupService.GetProductGroups();
|
||||||
|
|
||||||
|
private void OpenCreateDialog()
|
||||||
|
{
|
||||||
|
_productGroupToEdit = null;
|
||||||
|
_showProductGroupDialog = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpenEditDialog(ProductGroupEntity productGroup)
|
||||||
|
{
|
||||||
|
_productGroupToEdit = productGroup;
|
||||||
|
_showProductGroupDialog = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProductGroupSaved()
|
||||||
|
{
|
||||||
|
LoadProductGroups();
|
||||||
|
_orderHasChanged = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartDragging(ProductGroupEntity productGroup) =>
|
||||||
|
_draggedProductGroup = productGroup;
|
||||||
|
|
||||||
|
private void DropProductGroup(ProductGroupEntity target)
|
||||||
|
{
|
||||||
|
if (_draggedProductGroup == null || _draggedProductGroup.Id == target.Id ||
|
||||||
|
!string.IsNullOrWhiteSpace(_searchText))
|
||||||
|
{
|
||||||
|
StopDragging();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int oldIndex = _productGroups.IndexOf(_draggedProductGroup);
|
||||||
|
int newIndex = _productGroups.IndexOf(target);
|
||||||
|
_productGroups.RemoveAt(oldIndex);
|
||||||
|
_productGroups.Insert(newIndex, _draggedProductGroup);
|
||||||
|
_orderHasChanged = true;
|
||||||
|
StopDragging();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StopDragging() => _draggedProductGroup = null;
|
||||||
|
|
||||||
|
private string GetDragClass(ProductGroupEntity productGroup) =>
|
||||||
|
_draggedProductGroup?.Id == productGroup.Id ? "dragging" : string.Empty;
|
||||||
|
|
||||||
|
private void SaveOrder()
|
||||||
|
{
|
||||||
|
ProductGroupService.UpdateOrder(_productGroups.Select(item => item.Id).ToList());
|
||||||
|
_orderHasChanged = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ConfirmDeactivation(ProductGroupEntity productGroup)
|
||||||
|
{
|
||||||
|
DialogResult result = await DialogService.ShowConfirmationAsync(
|
||||||
|
$"Er du sikker på, at du vil slette {productGroup.Name}?",
|
||||||
|
"Bekræft sletning", "Slet", "Annuller");
|
||||||
|
|
||||||
|
if (!result.Cancelled)
|
||||||
|
{
|
||||||
|
ProductGroupService.ArchiveProductGroup(productGroup.Id);
|
||||||
|
LoadProductGroups();
|
||||||
|
_orderHasChanged = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
.products-page { width: min(100%, 1400px); margin: 0 auto; padding: 32px; }
|
||||||
|
.products-header { padding-bottom: 20px; border-bottom: 1px solid #e2e8f0; }
|
||||||
|
.products-header h1 { margin: 0; }
|
||||||
|
.products-toolbar { display: grid; grid-template-columns: minmax(280px, 1.6fr) minmax(210px, .8fr) minmax(230px, .9fr); gap: 32px; margin: 32px 0; }
|
||||||
|
.search-field { display: flex; align-items: center; gap: 16px; box-sizing: border-box; height: 56px; padding: 0 20px; border: 1px solid #cbd5e1; border-radius: 8px; color: #475569; }
|
||||||
|
.search-field span { font-size: 30px; }
|
||||||
|
.search-field input { width: 100%; border: 0; outline: 0; background: transparent; color: inherit; font: inherit; font-size: 18px; }
|
||||||
|
.products-toolbar ::deep fluent-button { width: 100%; min-height: 56px; font-size: 18px; }
|
||||||
|
.button-icon { margin-right: 10px; font-size: 26px; }
|
||||||
|
.products-table { overflow: hidden; border: 1px solid #cbd5e1; border-radius: 10px; }
|
||||||
|
.products-list { max-height: 535px; overflow-y: auto; }
|
||||||
|
.products-row { display: grid; grid-template-columns: 150px minmax(190px, 1.3fr) 110px 120px minmax(250px, 1fr); align-items: center; min-height: 76px; padding: 0 32px; border-bottom: 1px solid #e2e8f0; }
|
||||||
|
.products-column-header { min-height: 56px; color: #334155; font-weight: 600; }
|
||||||
|
.product-row:last-child { border-bottom: 0; }
|
||||||
|
.product-row.dragging { opacity: .45; }
|
||||||
|
.drag-handle { width: fit-content; color: #64748b; cursor: grab; font-size: 30px; }
|
||||||
|
.product-name { font-size: 19px; }
|
||||||
|
.active-status { color: #16a34a; }
|
||||||
|
.row-actions { display: flex; gap: 12px; }
|
||||||
|
.products-page ::deep .delete-button { color: #d13438; border-color: #d13438; }
|
||||||
|
.products-help { display: flex; gap: 12px; margin: 32px 6px 0; color: #64748b; }
|
||||||
|
.products-help span { font-size: 21px; }
|
||||||
|
.empty-state { margin: 0; padding: 36px; color: #64748b; text-align: center; }
|
||||||
|
@media (max-width: 900px) { .products-toolbar { grid-template-columns: 1fr; gap: 12px; } .products-table { overflow-x: auto; } .products-row { min-width: 900px; } }
|
||||||
18
PointOfSale/Pos.Web/Components/Pages/Settings/Settings.razor
Normal file
18
PointOfSale/Pos.Web/Components/Pages/Settings/Settings.razor
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
@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" onclick="@(() => Nav.NavigateTo("/settings/products"))">
|
||||||
|
Vare
|
||||||
|
</FluentButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 680px) {
|
||||||
|
.settings-page {
|
||||||
|
padding: 24px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
@page "/todaySales"
|
||||||
|
@rendermode InteractiveServer
|
||||||
|
@inject IJSRuntime Js
|
||||||
|
@inject SaleService SaleService
|
||||||
|
|
||||||
|
<Header />
|
||||||
|
<div class="page-container today-sales-page">
|
||||||
|
<div class="filters">
|
||||||
|
<label class="filter-control"><span>Dato</span><span class="filter-box"><svg viewBox="0 0 24 24"><rect x="3.5" y="5.5" width="17" height="15" rx="2"/><path d="M7 3.5v4M17 3.5v4M3.5 9.5h17"/></svg><input type="date" value="@_date.ToString("yyyy-MM-dd")" @onchange="DateChanged"/></span></label>
|
||||||
|
<label class="filter-control short"><span>Periode</span><span class="filter-box"><svg viewBox="0 0 24 24"><rect x="3.5" y="5.5" width="17" height="15" rx="2"/><path d="M7 3.5v4M17 3.5v4M3.5 9.5h17"/></svg><select @onchange="RangeChanged" value="@_range"><option value="1">1 dag</option><option value="7">7 dage</option><option value="30">30 dage</option></select></span></label>
|
||||||
|
</div>
|
||||||
|
<section class="ui-card summary-card">
|
||||||
|
<div class="summary-icon"><svg viewBox="0 0 64 64"><path d="M12 52h40M18 48V35h8v13M29 48V25h8v23M40 48V14h8v34"/></svg></div>
|
||||||
|
<div><p>Dagens salg</p><strong>@Currency(_summary.Total)</strong><small>@_summary.SaleCount salg</small></div>
|
||||||
|
</section>
|
||||||
|
<div class="sales-grid">
|
||||||
|
<UiCard Title="Salg pr. varegruppe" Class="sales-category-card">
|
||||||
|
<MetricList TItem="Metric" Items="@_summary.Categories">
|
||||||
|
<HeaderContent><span>Varegruppe</span><span>Salg</span></HeaderContent>
|
||||||
|
<RowTemplate Context="row"><div class="metric-row"><span>@row.Name</span><span>@Currency(row.Amount)</span></div></RowTemplate>
|
||||||
|
</MetricList>
|
||||||
|
</UiCard>
|
||||||
|
<div class="sales-right">
|
||||||
|
<UiCard Title="Betalingsfordeling">
|
||||||
|
<MetricList TItem="Metric" Items="@_summary.Payments">
|
||||||
|
<RowTemplate Context="row"><div class="metric-row"><span>@row.Name</span><span>@Currency(row.Amount)</span></div></RowTemplate>
|
||||||
|
</MetricList>
|
||||||
|
</UiCard>
|
||||||
|
<button class="print-button" type="button" @onclick="PrintAsync"><svg viewBox="0 0 24 24"><path d="M6 9V3h12v6M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2M6 14h12v7H6z"/></svg>Print salg</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using Microsoft.JSInterop;
|
||||||
|
using Pos.Models;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace Pos.Web.Components.Pages.TodaySales;
|
||||||
|
|
||||||
|
public partial class TodaySales
|
||||||
|
{
|
||||||
|
private static readonly CultureInfo Danish = CultureInfo.GetCultureInfo("da-DK");
|
||||||
|
private TotalSaleDetail? _totalSaleDetail;
|
||||||
|
private DateTime _date = DateTime.Now;
|
||||||
|
private string _range = "1";
|
||||||
|
private Summary _summary = new(0m, 0, Array.Empty<Metric>(), Array.Empty<Metric>());
|
||||||
|
|
||||||
|
private async Task DateChanged(ChangeEventArgs e)
|
||||||
|
{
|
||||||
|
if (DateTime.TryParse(e.Value?.ToString(), out var date))
|
||||||
|
{
|
||||||
|
_date = date;
|
||||||
|
await LoadAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
await LoadAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RangeChanged(ChangeEventArgs e)
|
||||||
|
{
|
||||||
|
_range = e.Value?.ToString() ?? "1";
|
||||||
|
await LoadAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task LoadAsync()
|
||||||
|
{
|
||||||
|
var result = SaleService.TotalSale(_date);
|
||||||
|
_totalSaleDetail = result;
|
||||||
|
|
||||||
|
_summary = new Summary(
|
||||||
|
result.TotalSale,
|
||||||
|
result.TotalCustomer,
|
||||||
|
result.TotalSaleCategories
|
||||||
|
.Select(category => new Metric(category.Category, category.Sale))
|
||||||
|
.ToList(),
|
||||||
|
result.PaymentBreakdown
|
||||||
|
.Select(payment => new Metric(payment.PaymentMethod, payment.Amount))
|
||||||
|
.ToList());
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task PrintAsync()
|
||||||
|
{
|
||||||
|
// TODO: Kobl til SaleService.PrintSaleOfDay(_date, _totalSaleDetail)
|
||||||
|
// eller POST /api/sales/print.
|
||||||
|
await Js.InvokeVoidAsync("window.print");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Currency(decimal amount) =>
|
||||||
|
$"{amount.ToString("N2", Danish)} kr.";
|
||||||
|
|
||||||
|
private sealed record Metric(string Name, decimal Amount);
|
||||||
|
|
||||||
|
private sealed record Summary(
|
||||||
|
decimal Total,
|
||||||
|
int SaleCount,
|
||||||
|
IReadOnlyList<Metric> Categories,
|
||||||
|
IReadOnlyList<Metric> Payments);
|
||||||
|
}
|
||||||
@@ -0,0 +1,307 @@
|
|||||||
|
|
||||||
|
.today-sales-page {
|
||||||
|
width: min(100%, 1420px);
|
||||||
|
min-height: calc(100dvh - 78px);
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 38px 32px 64px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters {
|
||||||
|
display: flex;
|
||||||
|
align-items: end;
|
||||||
|
gap: 16px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-control {
|
||||||
|
display: flex;
|
||||||
|
min-width: 230px;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
color: #3e444d;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-control.short {
|
||||||
|
min-width: 140px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-box {
|
||||||
|
display: flex;
|
||||||
|
min-height: 56px;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 0 14px;
|
||||||
|
border: 1px solid #bfc5cd;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-box svg {
|
||||||
|
width: 23px;
|
||||||
|
height: 23px;
|
||||||
|
flex: none;
|
||||||
|
fill: none;
|
||||||
|
stroke: currentColor;
|
||||||
|
stroke-linecap: round;
|
||||||
|
stroke-linejoin: round;
|
||||||
|
stroke-width: 1.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-box input,
|
||||||
|
.filter-box select {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
border: 0;
|
||||||
|
outline: 0;
|
||||||
|
background: transparent;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-card {
|
||||||
|
display: flex;
|
||||||
|
min-height: 170px;
|
||||||
|
align-items: center;
|
||||||
|
gap: 32px;
|
||||||
|
padding: 28px 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-icon {
|
||||||
|
display: grid;
|
||||||
|
width: 114px;
|
||||||
|
height: 114px;
|
||||||
|
flex: none;
|
||||||
|
place-items: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
color: var(--pos-blue);
|
||||||
|
background: var(--pos-blue-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-icon svg {
|
||||||
|
width: 58px;
|
||||||
|
height: 58px;
|
||||||
|
fill: none;
|
||||||
|
stroke: currentColor;
|
||||||
|
stroke-linecap: round;
|
||||||
|
stroke-linejoin: round;
|
||||||
|
stroke-width: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-card p {
|
||||||
|
margin: 0 0 4px;
|
||||||
|
font-size: 19px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-card strong {
|
||||||
|
display: block;
|
||||||
|
font-size: 48px;
|
||||||
|
line-height: 1.05;
|
||||||
|
letter-spacing: -.035em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-card small {
|
||||||
|
display: block;
|
||||||
|
margin-top: 6px;
|
||||||
|
color: var(--pos-muted);
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sales-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1.25fr) minmax(350px, 1fr);
|
||||||
|
gap: 20px;
|
||||||
|
align-items: start;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sales-right {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::deep .ui-card {
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid var(--pos-border);
|
||||||
|
border-radius: 12px;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 1px 2px rgba(16, 24, 40, .03);
|
||||||
|
}
|
||||||
|
|
||||||
|
::deep .ui-card__header {
|
||||||
|
padding: 26px 32px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::deep .ui-card__header h2 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 650;
|
||||||
|
}
|
||||||
|
|
||||||
|
::deep .ui-card__body {
|
||||||
|
padding: 0 32px 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::deep .metric-list__header,
|
||||||
|
::deep .metric-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr auto;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::deep .metric-list__header {
|
||||||
|
padding-bottom: 12px;
|
||||||
|
color: var(--pos-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
::deep .metric-row {
|
||||||
|
min-height: 58px;
|
||||||
|
align-items: center;
|
||||||
|
border-top: 1px solid var(--pos-border-soft);
|
||||||
|
font-size: 19px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::deep .metric-row > :last-child,
|
||||||
|
::deep .metric-list__header > :last-child {
|
||||||
|
text-align: right;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-button {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 70px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 14px;
|
||||||
|
border: 1.5px solid var(--pos-blue);
|
||||||
|
border-radius: 9px;
|
||||||
|
color: var(--pos-blue);
|
||||||
|
background: #fff;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-button:hover {
|
||||||
|
background: var(--pos-blue-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-button svg {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
fill: none;
|
||||||
|
stroke: currentColor;
|
||||||
|
stroke-linecap: round;
|
||||||
|
stroke-linejoin: round;
|
||||||
|
stroke-width: 1.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 900px) and (max-height: 850px) {
|
||||||
|
.today-sales-page {
|
||||||
|
padding-top: 22px;
|
||||||
|
padding-bottom: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters {
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-box {
|
||||||
|
min-height: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-card {
|
||||||
|
min-height: 130px;
|
||||||
|
gap: 24px;
|
||||||
|
padding: 20px 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-icon {
|
||||||
|
width: 88px;
|
||||||
|
height: 88px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-icon svg {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-card strong {
|
||||||
|
font-size: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-card small {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sales-grid {
|
||||||
|
gap: 20px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::deep .ui-card__header {
|
||||||
|
padding: 18px 28px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::deep .ui-card__body {
|
||||||
|
padding: 0 28px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::deep .metric-list__header {
|
||||||
|
padding-bottom: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::deep .metric-row {
|
||||||
|
min-height: 39px;
|
||||||
|
font-size: 17px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-button {
|
||||||
|
min-height: 54px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.sales-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 680px) {
|
||||||
|
.today-sales-page {
|
||||||
|
padding: 24px 16px 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-card {
|
||||||
|
gap: 18px;
|
||||||
|
padding: 22px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-icon {
|
||||||
|
width: 72px;
|
||||||
|
height: 72px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-icon svg {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-card strong {
|
||||||
|
font-size: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::deep .ui-card__header {
|
||||||
|
padding: 22px 20px 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::deep .ui-card__body {
|
||||||
|
padding: 0 20px 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::deep .metric-row {
|
||||||
|
font-size: 17px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
5
PointOfSale/Pos.Web/Components/Routes.razor
Normal file
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>
|
||||||
26
PointOfSale/Pos.Web/Components/Shared/Header.razor
Normal file
26
PointOfSale/Pos.Web/Components/Shared/Header.razor
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
@rendermode InteractiveServer
|
||||||
|
@implements IDisposable
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
|
||||||
|
<header class="top-nav">
|
||||||
|
<div class="nav-start">
|
||||||
|
@if (!IsHomePage)
|
||||||
|
{
|
||||||
|
<button class="home-button"
|
||||||
|
type="button"
|
||||||
|
title="Tilbage til forsiden"
|
||||||
|
aria-label="Tilbage til forsiden"
|
||||||
|
@onclick="GoToHome">
|
||||||
|
←
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="brand">Blomster POS</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<time datetime="@_currentTime.ToString("O")">
|
||||||
|
@_currentTime.ToString(
|
||||||
|
"dddd d. MMMM yyyy · HH:mm",
|
||||||
|
_danishCulture)
|
||||||
|
</time>
|
||||||
|
</header>
|
||||||
42
PointOfSale/Pos.Web/Components/Shared/Header.razor.cs
Normal file
42
PointOfSale/Pos.Web/Components/Shared/Header.razor.cs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
private bool IsHomePage =>
|
||||||
|
string.IsNullOrEmpty(
|
||||||
|
NavigationManager.ToBaseRelativePath(NavigationManager.Uri));
|
||||||
|
|
||||||
|
public Header()
|
||||||
|
{
|
||||||
|
_timer = new Timer(
|
||||||
|
UpdateTime,
|
||||||
|
null,
|
||||||
|
TimeSpan.FromMinutes(1),
|
||||||
|
TimeSpan.FromMinutes(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateTime(object? state)
|
||||||
|
{
|
||||||
|
_currentTime = DateTime.Now;
|
||||||
|
_ = InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GoToHome()
|
||||||
|
{
|
||||||
|
NavigationManager.NavigateTo("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_timer.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
54
PointOfSale/Pos.Web/Components/Shared/Header.razor.css
Normal file
54
PointOfSale/Pos.Web/Components/Shared/Header.razor.css
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-start {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand {
|
||||||
|
color: var(--colorNeutralForeground1);
|
||||||
|
font-size: 26px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-button {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
padding: 0;
|
||||||
|
border: 1px solid var(--colorNeutralStroke1);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--colorNeutralForeground1);
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 28px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-button:hover {
|
||||||
|
background: var(--colorNeutralBackground1Hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-button:focus-visible {
|
||||||
|
outline: 2px solid var(--colorStrokeFocus2);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
time {
|
||||||
|
color: var(--colorNeutralForeground2);
|
||||||
|
font-size: 18px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
10
PointOfSale/Pos.Web/Components/Shared/MetricList.razor
Normal file
10
PointOfSale/Pos.Web/Components/Shared/MetricList.razor
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
@typeparam TItem
|
||||||
|
<div class="metric-list">
|
||||||
|
@if (HeaderContent is not null) { <div class="metric-list__header">@HeaderContent</div> }
|
||||||
|
@foreach (var item in Items) { @RowTemplate(item) }
|
||||||
|
</div>
|
||||||
|
@code {
|
||||||
|
[Parameter] public IReadOnlyList<TItem> Items { get; set; } = Array.Empty<TItem>();
|
||||||
|
[Parameter] public RenderFragment? HeaderContent { get; set; }
|
||||||
|
[Parameter] public RenderFragment<TItem> RowTemplate { get; set; } = default!;
|
||||||
|
}
|
||||||
12
PointOfSale/Pos.Web/Components/Shared/UiCard.razor
Normal file
12
PointOfSale/Pos.Web/Components/Shared/UiCard.razor
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<section class="ui-card @Class">
|
||||||
|
@if (!string.IsNullOrWhiteSpace(Title))
|
||||||
|
{
|
||||||
|
<div class="ui-card__header"><h2>@Title</h2></div>
|
||||||
|
}
|
||||||
|
<div class="ui-card__body">@ChildContent</div>
|
||||||
|
</section>
|
||||||
|
@code {
|
||||||
|
[Parameter] public string Title { get; set; } = string.Empty;
|
||||||
|
[Parameter] public string Class { get; set; } = string.Empty;
|
||||||
|
[Parameter] public RenderFragment? ChildContent { get; set; }
|
||||||
|
}
|
||||||
18
PointOfSale/Pos.Web/Components/_Imports.razor
Normal file
18
PointOfSale/Pos.Web/Components/_Imports.razor
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
@using System.Net.Http
|
||||||
|
@using System.Net.Http.Json
|
||||||
|
@using Microsoft.AspNetCore.Components
|
||||||
|
@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
|
||||||
|
@using Pos.Service
|
||||||
|
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user