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,14 @@
[
{
ArticleCode: 'SERMFAE15',
IsEndOfLife: 'True'
},
{
ArticleCode: 'SERMFAE30',
IsEndOfLife: 'True'
},
{
ArticleCode: 'SERMFBE',
IsEndOfLife: 'True'
}
]
@@ -0,0 +1,50 @@
using System.Linq;
using Centron.BusinessLogic;
using Centron.BusinessLogic.EmployeeArea;
using Centron.BusinessLogic.Warehousing;
using Centron.BusinessLogic.WebServices.Employee;
using Centron.BusinessLogic.WebServices.Warehousing;
using Centron.Data.Entities.Merchandise.Articles;
using Centron.Interfaces.BL;
using Centron.Tests.EndToEnd.Infrastructure;
using Xunit;
using Xunit.Abstractions;
namespace Centron.Tests.EndToEnd.Tests.Administration.Employees;
public class EmployeeArticleParentTests : EndToEndTest
{
public EmployeeArticleParentTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper)
{
}
public override void Execute()
{
using (var session = new BLSession())
{
// Test case 1: Set article with the I3D 11 to have a parent article with I3D 14
var article = session.GetBL<ArticleWebServiceBL>().GetArticleByArticleI3D(11);
article.ParentArticleI3D = 14;
var saveResult = session.GetBL<ArticleWebServiceBL>().SaveArticle(article, base.GetLoggedInUser());
Assert.True(saveResult.Status == ResultStatus.Success, saveResult.Message);
// Test case 2: Check if we get the article with I3D 11 when searching for articles with parent article I3D 14 from Employee with I3D 14
var bl = session.GetBL<EmployeeArticleWebServiceBL>();
var searchResult = bl.GetEmployeeArticlesAsArticle(new EmployeeArticleFilter()
{
EmployeeI3D = 25,
ParentArticleI3D = 14
}, base.GetLoggedInUser().User);
Assert.True(searchResult.FirstOrDefault()?.I3D == 11, $"Expected article with I3D 11, but found: {searchResult.FirstOrDefault()?.I3D}");
// Test case 3 : Check if we can retrieve the parent article of article with I3D 11
var articleBL = session.GetBL<ArticleWebServiceBL>();
var articleSearchResult = articleBL.GetArticlesThroughPaging(ArticlePreviewSort.ArticleCode, false,
new ArticleCompactFilter()
{
IsParentArticle = true
}, 1, 100);
Assert.True(articleSearchResult.Result.First().I3D == 14, $"Expected parent article with I3D 14, but found: {articleSearchResult.Result.First().I3D}");
}
}
}
@@ -0,0 +1,153 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Centron.BusinessLogic;
using Centron.BusinessLogic.Sales.CustomerAssets.TimerBilling;
using Centron.BusinessLogic.WebServices.Employee;
using Centron.Interfaces.BL;
using Centron.Tests.EndToEnd.Infrastructure;
using CentronSoftware.Centron.WebServices.Entities.Sales.CustomerAssets.TimerBilling;
using Xunit.Abstractions;
namespace Centron.Tests.EndToEnd.Tests.Administration.Employees;
/// <summary>
/// Regression tests for ticket 168634.
/// Deactivating an employee used to delete the employee-article assignment (Mitarbeiterartikel).
/// That assignment is the only link between a recorded time and its employee in the billing modules,
/// so the deactivation silently made all already recorded times of that employee unbillable - and a
/// reactivation could not restore it.
/// These tests cover both halves of the fix: the assignment now survives a deactivation (only the
/// articles are marked as end-of-life), and the billing search falls back to the employee who
/// recorded the time when the assignment is missing.
/// </summary>
public class EmployeeDeactivationBillingTests : EndToEndTest
{
public EmployeeDeactivationBillingTests(ITestOutputHelper testOutputHelper)
: base(testOutputHelper)
{
}
public override void Execute()
{
var appUserI3D = this.GetAppUserWithBillableTimers();
var employeeI3D = this.Sql<int>($"SELECT Personal FROM dbo.Sichbenu WHERE I3D = {appUserI3D}");
this.DeactivationKeepsAssignmentAndMarksArticlesEndOfLife(appUserI3D);
this.ReactivationRevokesEndOfLife(appUserI3D);
this.TimersStayAttributedWhenAssignmentIsMissing(appUserI3D, employeeI3D);
}
/// <summary>
/// Finds an app-user that has employee articles which are used by open (not yet billed) timers
/// recorded by that same employee.
/// </summary>
private int GetAppUserWithBillableTimers()
{
var appUserI3D = this.Sql<int>(@"
SELECT TOP 1 SB.I3D
FROM dbo.hlpdsk_timer HT
INNER JOIN dbo.Mitarbeiterartikel MA ON MA.ArtikelI3D = HT.ArtikelI3D
INNER JOIN dbo.Sichbenu SB ON SB.I3D = MA.MitarbeiterI3D
WHERE ISNULL(HT.RechPosI3D, 0) = 0
AND ISNULL(HT.LiefPosI3D, 0) = 0
AND ISNULL(HT.AufPosI3D, 0) = 0
AND HT.PersonalI3D = SB.Personal
GROUP BY SB.I3D
ORDER BY COUNT(*) DESC, SB.I3D");
if (appUserI3D <= 0)
throw new InvalidOperationException("No app-user with employee articles and open timers found in the test database.");
return appUserI3D;
}
/// <summary>
/// Deactivating an employee must keep the employee-article assignment and only mark the
/// assigned articles as end-of-life, so the articles disappear from new selections while
/// the recorded times stay attributable.
/// </summary>
private void DeactivationKeepsAssignmentAndMarksArticlesEndOfLife(int appUserI3D)
{
// Reset the end-of-life flag first, so the snapshot is independent of the backup's state.
this.Sql($@"
UPDATE A SET A.EOL = 0
FROM dbo.ARTIK A
INNER JOIN dbo.Mitarbeiterartikel MA ON MA.ArtikelI3D = A.I3D
WHERE MA.MitarbeiterI3D = {appUserI3D}");
this.SetEmployeeArticlesEndOfLife(appUserI3D, true);
this.VerifyEmployeeArticles("Deactivation_AssignmentKeptAndArticlesEndOfLife", appUserI3D);
}
/// <summary>
/// Reactivating an employee must revoke the end-of-life mark that the deactivation has set.
/// </summary>
private void ReactivationRevokesEndOfLife(int appUserI3D)
{
this.SetEmployeeArticlesEndOfLife(appUserI3D, false);
this.VerifyEmployeeArticles("Reactivation_ArticlesUsableAgain", appUserI3D);
}
/// <summary>
/// Databases that were damaged by the old deactivation behaviour have no employee-article
/// assignment left. The billing search must still attribute those times to the employee who
/// recorded them, otherwise the times can never be billed again.
/// Times that merely use one of the employee's articles but were recorded by somebody else
/// move to that other employee, which is the correct attribution once the assignment is gone.
/// </summary>
private void TimersStayAttributedWhenAssignmentIsMissing(int appUserI3D, int employeeI3D)
{
var timersBefore = this.SearchTimersForEmployee(employeeI3D);
if (timersBefore.Any() == false)
throw new InvalidOperationException($"No open timers found for employee {employeeI3D} in the test database.");
// Simulate a database that was damaged by the old deactivation behaviour.
this.Sql($"DELETE FROM dbo.Mitarbeiterartikel WHERE MitarbeiterI3D = {appUserI3D}");
var timersAfter = this.SearchTimersForEmployee(employeeI3D);
this.Verifier.Verify("TimerBillingSearch_WithoutEmployeeArticleAssignment", new
{
AssignmentsLeft = this.Sql<int>($"SELECT COUNT(*) FROM dbo.Mitarbeiterartikel WHERE MitarbeiterI3D = {appUserI3D}"),
TimersFoundBefore = timersBefore.Count,
TimersRecordedByEmployeeBefore = timersBefore.Count(timer => timer.CreatorEmployeeI3D == employeeI3D),
TimersFoundAfter = timersAfter.Count,
TimersRecordedByEmployeeAfter = timersAfter.Count(timer => timer.CreatorEmployeeI3D == employeeI3D)
});
}
private void SetEmployeeArticlesEndOfLife(int appUserI3D, bool isEndOfLife)
{
using (var session = new BLSession())
{
session.GetBL<EmployeeArticleWebServiceBL>()
.SetEmployeeArticlesEndOfLife(this.GetLoggedInUser(), appUserI3D, isEndOfLife)
.ThrowIfError();
}
}
private void VerifyEmployeeArticles(string name, int appUserI3D)
{
this.Verifier.VerifySql(name, $@"
SELECT ArticleCode = A.Artikelcode,
IsEndOfLife = CAST(ISNULL(A.EOL, 0) AS bit)
FROM dbo.Mitarbeiterartikel MA
INNER JOIN dbo.ARTIK A ON A.I3D = MA.ArtikelI3D
WHERE MA.MitarbeiterI3D = {appUserI3D}
ORDER BY A.Artikelcode");
}
private List<TimerForTimerBillingDTO> SearchTimersForEmployee(int employeeI3D)
{
using (var session = new BLSession())
{
return session.GetBL<TimerBillingBL>()
.SearchTimers(new TimerBillingFilter { EmployeeI3Ds = new List<int> { employeeI3D } })
.ToList();
}
}
}
@@ -0,0 +1,14 @@
[
{
ArticleCode: 'SERMFAE15',
IsEndOfLife: 'False'
},
{
ArticleCode: 'SERMFAE30',
IsEndOfLife: 'False'
},
{
ArticleCode: 'SERMFBE',
IsEndOfLife: 'False'
}
]
@@ -0,0 +1,7 @@
{
AssignmentsLeft: 0,
TimersFoundAfter: 92,
TimersFoundBefore: 106,
TimersRecordedByEmployeeAfter: 92,
TimersRecordedByEmployeeBefore: 92
}