Codebasis als Dateien ins Arbeitsrepo statt als Gitlink

QuellCode/CentronERP war nur als Gitlink (Submodul-Referenz auf 79c1142)
getrackt, ohne .gitmodules und ohne erreichbares Remote. Der
Untersuchungsgegenstand der Versuchsreihe war damit nicht reproduzierbar
gesichert: Ein Klon haette ein leeres Verzeichnis erhalten, und die Belege
der 3.287 Anforderungen waeren nicht ueberpruefbar gewesen.

Umstellung:
- Historie nach c:\DEV\CentronERP_git_snapshot_79c1142 ausgelagert
  (vollstaendig lesbar, enthaelt 79c1142 und Vorgaenger 89ccfd6)
- Gitlink aus dem Index entfernt
- Dateiinhalt aufgenommen: 24.557 Dateien, rund 333 MB

Die verschachtelte .gitignore der Codebasis gilt weiter, Build-Artefakte
bleiben ausgeschlossen. Details in Versuche/Versuch_01/_Codebasis-Nachweis.md
This commit is contained in:
Christoph Schwörer
2026-08-26 07:43:51 +02:00
parent 18edae75b6
commit f045b99a25
24664 changed files with 5846716 additions and 1 deletions
@@ -0,0 +1,186 @@
using System;
using Centron.BusinessLogic;
using Centron.BusinessLogic.WebServices.Sales.Receipts.MasterDataLists;
using Centron.Tests.EndToEnd.Infrastructure;
using Xunit.Abstractions;
namespace Centron.Tests.EndToEnd.Tests.MasterDataList
{
/// <summary>
/// Regression tests for ticket 163981 — removing or replacing the main device's
/// serial number on a Stammblatt and propagating the change to its click counters.
/// </summary>
public class MainDeviceSerialNumberTests : EndToEndTest
{
public MainDeviceSerialNumberTests(ITestOutputHelper testOutputHelper)
: base(testOutputHelper)
{
}
public override void Execute()
{
this.RemoveMainDeviceSerialNumber_NoCounters();
this.RemoveMainDeviceSerialNumber_WithCounters_Fails();
this.SwapMainDeviceSerialNumber_WithCounters_UpdatesAll();
}
private void RemoveMainDeviceSerialNumber_NoCounters()
{
// Find a Stammblatt with a serial number but no active click counters.
// Use the MasterDataList view so the test sees the same SerialNumberI3D the BL does
// (the view derives it from Barcode.Status = 9, not from GeraeteKopf.SeriennummerI3D).
var masterDataListI3D = this.Sql<int>(@"
SELECT TOP 1 mdl.I3D
FROM MasterDataList mdl
WHERE mdl.SerialNumberI3D > 0
AND NOT EXISTS (
SELECT 1 FROM GeraeteClickZaehler gz
WHERE gz.GeraeteKopfI3D = mdl.I3D AND gz.SeriennummerI3D = mdl.SerialNumberI3D AND gz.Status = 1
)
ORDER BY mdl.I3D");
if (masterDataListI3D <= 0)
throw new InvalidOperationException("Precondition failed: no master data list with a serial number and without active click counters found.");
var oldSerialNumber = this.Sql<string>($"SELECT SerialNumber FROM MasterDataList WHERE I3D = {masterDataListI3D}");
var historyCutoffI3D = this.GetCurrentMaxHistoryI3D();
using var bl = new BLSession();
var loggedInUser = this.GetLoggedInUser();
var result = bl.GetBL<MasterDataListWebServiceBL>().RemoveMainDeviceSerialNumber(masterDataListI3D, loggedInUser);
this.Verifier.Verify("RemoveMainDeviceSerialNumber_NoCounters_Result", result);
this.Verifier.VerifySql(
"RemoveMainDeviceSerialNumber_NoCounters_Db",
$"SELECT I3D, SeriennummerI3D, Seriennummer FROM GeraeteKopf WHERE I3D = {masterDataListI3D}");
this.Verifier.VerifySql(
"RemoveMainDeviceSerialNumber_NoCounters_History",
BuildSerialNumberHistoryQuery(
masterDataListI3D,
historyCutoffI3D,
loggedInUser.User.Employee.I3D,
$"Seriennummer vom Hauptgerät \"{oldSerialNumber}\" wurde entfernt"));
}
private void RemoveMainDeviceSerialNumber_WithCounters_Fails()
{
// Find a Stammblatt with a serial number that still has active counters referencing it.
var masterDataListI3D = this.Sql<int>(@"
SELECT TOP 1 mdl.I3D
FROM MasterDataList mdl
INNER JOIN GeraeteClickZaehler gz ON gz.GeraeteKopfI3D = mdl.I3D AND gz.SeriennummerI3D = mdl.SerialNumberI3D AND gz.Status = 1
WHERE mdl.SerialNumberI3D > 0
ORDER BY mdl.I3D");
if (masterDataListI3D <= 0)
throw new InvalidOperationException("Precondition failed: no master data list with a serial number and active click counters found.");
using var bl = new BLSession();
var loggedInUser = this.GetLoggedInUser();
var result = bl.GetBL<MasterDataListWebServiceBL>().RemoveMainDeviceSerialNumber(masterDataListI3D, loggedInUser);
this.Verifier.Verify("RemoveMainDeviceSerialNumber_WithCounters_Fails_Result", new
{
result.Status,
result.Message
});
}
private void SwapMainDeviceSerialNumber_WithCounters_UpdatesAll()
{
// Find a Stammblatt with serial + active counters AND for which a different in-stock barcode
// (state = 1) for the same article exists — both must hold for the swap to be exercisable.
var masterDataListI3D = this.Sql<int>(@"
SELECT TOP 1 mdl.I3D
FROM MasterDataList mdl
INNER JOIN MasterDataListItems mi ON mi.HeadI3D = mdl.I3D AND mi.IsMainItem = 1
INNER JOIN GeraeteClickZaehler gz ON gz.GeraeteKopfI3D = mdl.I3D AND gz.SeriennummerI3D = mdl.SerialNumberI3D AND gz.Status = 1
WHERE mdl.SerialNumberI3D > 0
AND EXISTS (
SELECT 1 FROM Barcode b
WHERE b.ArtikelI3D = mi.ArticleI3D
AND b.Status = 1
AND b.I3D <> mdl.SerialNumberI3D
)
ORDER BY mdl.I3D");
if (masterDataListI3D <= 0)
throw new InvalidOperationException("Precondition failed: no master data list with a serial number, active click counters, and an alternative in-stock barcode for the main item's article found.");
// Pick the first available in-stock barcode (state = 1) that matches the main item's article.
var newSerialNumberI3D = this.Sql<int>($@"
SELECT TOP 1 b.I3D
FROM Barcode b
INNER JOIN MasterDataList mdl ON mdl.I3D = {masterDataListI3D}
INNER JOIN MasterDataListItems mi ON mi.HeadI3D = mdl.I3D AND mi.IsMainItem = 1
WHERE b.ArtikelI3D = mi.ArticleI3D
AND b.Status = 1
AND b.I3D <> mdl.SerialNumberI3D
ORDER BY b.I3D");
if (newSerialNumberI3D <= 0)
throw new InvalidOperationException("Precondition failed: no alternative in-stock barcode found for the main item's article.");
var oldSerialNumber = this.Sql<string>($"SELECT SerialNumber FROM MasterDataList WHERE I3D = {masterDataListI3D}");
var newSerialNumber = this.Sql<string>($"SELECT Barcode FROM Barcode WHERE I3D = {newSerialNumberI3D}");
var historyCutoffI3D = this.GetCurrentMaxHistoryI3D();
using var bl = new BLSession();
var loggedInUser = this.GetLoggedInUser();
var result = bl.GetBL<MasterDataListWebServiceBL>().SwapMainDeviceSerialNumber(masterDataListI3D, newSerialNumberI3D, loggedInUser);
this.Verifier.Verify("SwapMainDeviceSerialNumber_WithCounters_UpdatesAll_Result", new
{
result.Status,
result.Message
});
this.Verifier.VerifySql(
"SwapMainDeviceSerialNumber_WithCounters_UpdatesAll_DbHead",
$"SELECT I3D, SeriennummerI3D, Seriennummer FROM GeraeteKopf WHERE I3D = {masterDataListI3D}");
this.Verifier.VerifySql(
"SwapMainDeviceSerialNumber_WithCounters_UpdatesAll_DbCounters",
$"SELECT I3D, GeraeteKopfI3D, SeriennummerI3D FROM GeraeteClickZaehler WHERE GeraeteKopfI3D = {masterDataListI3D} ORDER BY I3D");
this.Verifier.VerifySql(
"SwapMainDeviceSerialNumber_WithCounters_UpdatesAll_History",
BuildSerialNumberHistoryQuery(
masterDataListI3D,
historyCutoffI3D,
loggedInUser.User.Employee.I3D,
$"Seriennummer vom Hauptgerät wurde getauscht von \"{oldSerialNumber}\" auf \"{newSerialNumber}\""));
}
/// <summary>
/// Highest AnlageLog I3D before the operation under test runs, used as a cutoff so the history
/// assertion only sees rows this test actually produced and not leftovers from earlier runs.
/// </summary>
private int GetCurrentMaxHistoryI3D()
{
return this.Sql<int>("SELECT ISNULL(MAX(I3D), 0) FROM AnlageLog");
}
/// <summary>
/// Reads the Stammblatt history entries (AnlageLog) that the operation under test wrote.
/// The cutoff limits the result to rows this run produced, so leftovers from earlier runs cannot
/// satisfy the check, and an unexpected additional entry shows up as an extra row in the snapshot.
/// MatchesExpected covers the exact description including old and new serial number.
/// Employee and date are compared as flags because their raw values differ per run.
/// </summary>
private static string BuildSerialNumberHistoryQuery(int masterDataListI3D, int historyCutoffI3D, int expectedEmployeeI3D, string expectedDescription)
{
var expectedLiteral = expectedDescription.Replace("'", "''");
return $@"
SELECT Beschreibung,
CASE WHEN Beschreibung = N'{expectedLiteral}' THEN 1 ELSE 0 END AS MatchesExpected,
CASE WHEN PersonalI3D = {expectedEmployeeI3D} THEN 1 ELSE 0 END AS IsExpectedEmployee,
CASE WHEN Datum IS NOT NULL THEN 1 ELSE 0 END AS HasDate
FROM AnlageLog
WHERE AnlageI3D = {masterDataListI3D}
AND I3D > {historyCutoffI3D}
ORDER BY I3D";
}
}
}
@@ -0,0 +1,56 @@
using System.Collections.Generic;
using System.Linq;
using Centron.BusinessLogic;
using Centron.BusinessLogic.EmployeeArea;
using Centron.BusinessLogic.WebServices.Sales.Receipts.MasterDataLists;
using Centron.Tests.EndToEnd.Infrastructure;
using CentronSoftware.Centron.WebServices.Entities.Sales.Receipts.MasterDataLists;
using Xunit.Abstractions;
namespace Centron.Tests.EndToEnd.Tests.MasterDataList
{
public class MasterDataListTests : EndToEndTest
{
public MasterDataListTests(ITestOutputHelper testOutputHelper)
: base(testOutputHelper)
{
}
public override void Execute()
{
this.CheckMasterDataMultiplyingBug();
this.GetOverview();
}
private void CheckMasterDataMultiplyingBug()
{
// Known case one: We have multiple barcodes for the same MasterDataList
using var bl = new BLSession();
bl.DAOSession.Advanced.RawSqlAccess.ExecuteNonQueryTransactionSave(@"UPDATE Barcode SET GeraetePosI3D = 180 WHERE I3D = 9");
// Known case two: We had the MasterDataList already in a other contract
bl.DAOSession.Advanced.RawSqlAccess.ExecuteNonQueryTransactionSave(@"INSERT INTO VertragGeraete (VertragKopfI3D, GeraeteKopfI3D, Status) VALUES (7, 13, 0)");
// If you know other issues which resulted in multiplying data, please add them here :-)
// Verify the View returns us no data
Verifier.VerifySql("MasterDataListTests_CheckMasterDataMultiplyingBug",
"select md.I3D, COUNT(*) cnt from MasterDataList md group by md.I3D HAVING COUNT(*) > 1");
}
private void GetOverview()
{
using (var bl = new BLSession())
{
var filter = new MasterDataListSearchFilter
{
State = true
};
var overview = bl.GetBL<MasterDataListWebServiceBL>().GetMasterDataListOverview(filter);
this.Verifier.Verify("MasterDataListOverview", overview.Data);
}
}
}
}
@@ -0,0 +1,7 @@
[
{
I3D: '12',
SeriennummerI3D: null,
Seriennummer: null
}
]
@@ -0,0 +1,8 @@
[
{
Beschreibung: 'Seriennummer vom Hauptgerät "SNMFConsulting1" wurde entfernt',
MatchesExpected: '1',
IsExpectedEmployee: '1',
HasDate: '1'
}
]
@@ -0,0 +1,109 @@
{
Data: {
AddressI3D: null,
ArticleCode: 'CB413A#B19',
Caption: 'Hewlett Packard LASERJET P1505NPlus 23PPM USB 2.0\r\nLaserdrucker s/w/ 600x600 dpi/ Druckgeschwindigkeit Mono: 23 ppm/ Speicher: 8 MB/ Formate: Briefumschläge, A4, A5, A6, B5, Postkarten/ Zufuhr: 260 Blatt/ Duplex: manuell/ Papierablage Standard: 150 Blatt/ Papiergew. min. - max.: 60 - 163 g/m2/ USB 2.0, 12 Monate HP Garantie',
ChangedAt: DateTime,
ChangedByI3D: 22,
ChangedThroughApplicationVersion: '1.0.0.0',
ContactPerson: '',
ContactPersonI3D: null,
ContractI3D: 6,
CounterDevice: 0,
CreatedAt: DateTime,
CreatedByI3D: 31,
CreatedThroughApplicationVersion: '',
CurrencyFactor: 1.0,
CurrencyI3D: null,
CurrencyString: '',
CustomerI3D: 10009,
CustomerName: 'MF Consulting',
Date: null,
DirectoryI3D: 1002,
FreeInventoryNumber: '',
I3D: 12,
Info: '',
InfoShort: '',
InvoiceDate: null,
InvoiceI3D: null,
InvoiceItemI3D: null,
InvoiceNumber: null,
IsActive: true,
IsMsp: false,
IsNew: false,
Items: [
{
ArticleCode: 'CB413A#B19',
ArticleI3D: 69,
Barcodes: [],
ContractPrice: null,
ContractPurchasePrice: null,
ContractText: null,
EANCode: '0883585165186',
Expanded: null,
FontColor: 32768,
FontName: 'Tahoma',
FontSize: 9,
FontStyle: 2,
FreeText: '',
GroupID: 0,
HeadI3D: 12,
I3D: 40,
Indent: 0,
InternalPosition: 0,
InvoiceItemI3D: 0,
IsContractRelevant: false,
IsMainItem: true,
ItemKind: 1,
ManufacturerCode: '',
PurchasePrice: 0.0,
Quantity: 1.0,
ReceiptItemI3D: null,
RichText: '',
SellPrice: 0.0,
Text: 'Hewlett Packard LASERJET P1505NPlus 23PPM USB 2.0\r\nLaserdrucker s/w/ 600x600 dpi/ Druckgeschwindigkeit Mono: 23 ppm/ Speicher: 8 MB/ Formate: Briefumschläge, A4, A5, A6, B5, Postkarten/ Zufuhr: 260 Blatt/ Duplex: manuell/ Papierablage Standard: 150 Blatt/ Papiergew. min. - max.: 60 - 163 g/m2/ USB 2.0, 12 Monate HP Garantie',
Visible: 1
},
{
ArticleCode: '34543ER',
ArticleI3D: 46,
Barcodes: [],
ContractPrice: null,
ContractPurchasePrice: null,
ContractText: null,
EANCode: '',
Expanded: null,
FontColor: 0,
FontName: 'Tahoma',
FontSize: 9,
FontStyle: 0,
FreeText: '',
GroupID: 0,
HeadI3D: 12,
I3D: 41,
Indent: 0,
InternalPosition: 1,
InvoiceItemI3D: 0,
IsContractRelevant: false,
IsMainItem: false,
ItemKind: 1,
ManufacturerCode: '',
PurchasePrice: 0.0,
Quantity: 1.0,
ReceiptItemI3D: null,
RichText: '',
SellPrice: 0.0,
Text: 'USB Kabel 3 Meter, grau',
Visible: 1
}
],
Number: 12,
QuelleReceiptKind: null,
SerialNumber: null,
SerialNumberI3D: null
},
Error: null,
Message: null,
MessageCode: null,
Status: 0
}
@@ -0,0 +1,4 @@
{
Message: 'Seriennummer kann nicht entfernt werden, weil noch aktive Zähler hängen. Bitte die Seriennummer tauschen.',
Status: 2
}
@@ -0,0 +1,7 @@
[
{
I3D: '16',
GeraeteKopfI3D: '18',
SeriennummerI3D: '75'
}
]
@@ -0,0 +1,7 @@
[
{
I3D: '18',
SeriennummerI3D: '75',
Seriennummer: '13'
}
]
@@ -0,0 +1,8 @@
[
{
Beschreibung: 'Seriennummer vom Hauptgerät wurde getauscht von "1" auf "13"',
MatchesExpected: '1',
IsExpectedEmployee: '1',
HasDate: '1'
}
]