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,410 @@
{
Data: {
Action: {
ArticleWorkItemI3D: null,
Category: {
Description: null,
I3D: 6,
IsDeactivated: null,
IsPrivate: null,
Name: 'Hardware',
Number: 2,
ParentI3D: 1,
ServiceBoardWebColor: null,
ServiceBoardWebIcon: null,
SkillAssignments: [],
SubCategories: [
{
Description: null,
I3D: 7,
IsDeactivated: null,
IsPrivate: null,
Name: 'Raid',
Number: 1,
ParentI3D: 6,
ServiceBoardWebColor: null,
ServiceBoardWebIcon: null,
SkillAssignments: [],
SubCategories: []
},
{
Description: null,
I3D: 8,
IsDeactivated: null,
IsPrivate: null,
Name: 'Motherboard',
Number: 2,
ParentI3D: 6,
ServiceBoardWebColor: null,
ServiceBoardWebIcon: null,
SkillAssignments: [],
SubCategories: []
},
{
Description: null,
I3D: 52,
IsDeactivated: null,
IsPrivate: null,
Name: 'Festplatten',
Number: 3,
ParentI3D: 6,
ServiceBoardWebColor: null,
ServiceBoardWebIcon: null,
SkillAssignments: [],
SubCategories: []
},
{
Description: null,
I3D: 53,
IsDeactivated: null,
IsPrivate: null,
Name: 'Redundante Lüfter',
Number: 4,
ParentI3D: 6,
ServiceBoardWebColor: null,
ServiceBoardWebIcon: null,
SkillAssignments: [],
SubCategories: []
},
{
Description: null,
I3D: 61,
IsDeactivated: null,
IsPrivate: null,
Name: 'RAID System',
Number: 5,
ParentI3D: 6,
ServiceBoardWebColor: null,
ServiceBoardWebIcon: null,
SkillAssignments: [],
SubCategories: []
},
{
Description: null,
I3D: 62,
IsDeactivated: null,
IsPrivate: null,
Name: 'Lüfter',
Number: 6,
ParentI3D: 6,
ServiceBoardWebColor: null,
ServiceBoardWebIcon: null,
SkillAssignments: [],
SubCategories: []
}
]
},
Checklists: [],
ContactPersonOldReferenceI3D: null,
Contract: {
AdditionalText: '',
AutomaticExtensionAmountMonths: 0,
AutomaticExtensionFlag: false,
Begin: DateTime,
ContingentKind: 0,
ContingentOverbooking: 0,
ContractBegin: DateTime,
ContractEnd: DateTime,
ContractKind: {
Color: -16777195,
Description: 'Vertrag für Kontingent',
I3D: 3,
Name: 'Kontingent',
State: 1
},
CreateInvoiceOnCorporation: false,
CurrencyString: '€',
CustomerI3D: 10000,
DeductionContingentIntervalKind: 1,
DeductionContingentIntervalValue: 1,
DeductionIntervalKind: 1,
DeductionIntervalValue: 3,
Duration: null,
DurationKind: 2,
Editor: null,
ForeignContract: 0,
ForeignContractText: '',
I3D: 35,
IsClick: 0,
IsContingent: 1,
IsDisplayedOnWeb: false,
IsGeneralAgreement: 0,
IsHiddenInHelpdesk: false,
Name: '',
NetPriceFC: 0.0,
NetPriceFCComplete: 0.0,
Number: 14035,
PurchaseOrderNumber: '',
Receiver: 'c-entron software gmbh\r\nsdklfnfdjskfdsöfjsadkfjödöfjdsjafsöjdkfjösdajfsjdföjfjsdjfkjsadfjskdjfkjasdköal kalsjsvkjkldsjfkldösjkl jksdljvk jklsavj kjdslk jkldsj klödsj kljasdkl jkl jsckl jklasj klasj kljsö jksj dkljsdklj klsdj öklsdj klsdj lkjkasdj klasdj klsdj kljasfkl jöl\r\nFrau Franziska Franzen\r\nListstrasse 1\r\n89079 Ulm',
ReferenceNumber: '',
SLAPriority: 5,
State: 1,
TerminationDate: DateTime,
WebReportI3D: null
},
Customer: {
Adviser1: {
Availability: 0,
BranchI3D: 1,
DefaultStorageI3D: null,
Dispatcher: false,
DisplayText: 'Aiello, Angelina (AAI)',
Email: 'aiello@c-entron.de',
Employeenumber: 0,
FirstName: 'Angelina',
FullName: 'Aiello, Angelina (AAI)',
I3D: 38,
IsActive: true,
LastAvailabilityChange: null,
LastName: 'Aiello',
LeavingDate: DateTime,
Phone1: '',
RemainingDaysOfVacation: 0.0,
ShortSign: 'AAI',
State: 1,
SupportLevel: null,
Template1: false,
Template2: false,
Template3: false,
Template4: false,
VacationEntitlement: 0.0
},
Adviser2: {
Availability: 0,
BranchI3D: 0,
DefaultStorageI3D: null,
Dispatcher: false,
DisplayText: 'Hoffmann, Thomas (ABO)',
Email: 'lehnert@c-entron.de',
Employeenumber: 0,
FirstName: 'Thomas',
FullName: 'Hoffmann, Thomas (ABO)',
I3D: 27,
IsActive: true,
LastAvailabilityChange: null,
LastName: 'Hoffmann',
LeavingDate: DateTime,
Phone1: '0731-140399-333',
RemainingDaysOfVacation: 0.0,
ShortSign: 'ABO',
State: 1,
SupportLevel: null,
Template1: false,
Template2: false,
Template3: false,
Template4: false,
VacationEntitlement: 0.0
},
Adviser3: null,
Adviser4: null,
Adviser5: null,
Adviser6: null,
DefaultAddress: null,
EMail: '',
Fax: '',
HelpdeskClosingDontNotifyCustomer: null,
I3D: 10022,
IsCustomerKind1: false,
IsCustomerKind2: false,
IsCustomerKind3: false,
IsCustomerKind4: false,
IsCustomerKind5: false,
Locked: false,
Matchcode: '',
Name: 'Hubert Meier',
Phone: '',
RootDirI3D: 4359,
Status: 1,
Template1: false,
Template2: false,
Template3: false,
Template4: false,
Website: ''
},
Departments: [],
DueDateOffset: 0,
I3D: 9,
InternalNote: null,
IsImportant: false,
IsOnlyInternalVisible: false,
MasterDataList: null,
Members: [
{
Employee: {
Availability: 0,
BranchI3D: 0,
DefaultStorageI3D: null,
Dispatcher: false,
DisplayText: 'Administrator, c-entron (ADMIN)',
Email: 'vertrieb@c-entron.de',
Employeenumber: 0,
FirstName: 'c-entron',
FullName: 'Administrator, c-entron (ADMIN)',
I3D: 22,
IsActive: true,
LastAvailabilityChange: null,
LastName: 'Administrator',
LeavingDate: DateTime,
Phone1: '0731-140399-0',
RemainingDaysOfVacation: 28.0,
ShortSign: 'ADMIN',
State: 1,
SupportLevel: null,
Template1: false,
Template2: false,
Template3: false,
Template4: false,
VacationEntitlement: 30.0
},
I3D: 15
}
],
PlannedDurationInHours: null,
Priority: {
Color: '#FF0000FF',
DueDateDelayInHours: 2.0,
EscalationSa: false,
EscalationSo: false,
Hour2: 4.0,
Hour3: 8.0,
I3D: 1,
IsDeactivated: null,
IsSLA: true,
Name: 'sofort (2 Stunden)',
Number: null,
OfficeHourFrom: DateTime,
OfficeHourTo: DateTime,
RegularWorkingDay: 2.0,
ServiceBoardWebColor: null,
ServiceBoardWebIcon: null,
State: 1
},
ResponsiblePerson: {
Availability: 0,
BranchI3D: 0,
DefaultStorageI3D: null,
Dispatcher: false,
DisplayText: 'Administrator, c-entron (ADMIN)',
Email: 'vertrieb@c-entron.de',
Employeenumber: 0,
FirstName: 'c-entron',
FullName: 'Administrator, c-entron (ADMIN)',
I3D: 22,
IsActive: true,
LastAvailabilityChange: null,
LastName: 'Administrator',
LeavingDate: DateTime,
Phone1: '0731-140399-0',
RemainingDaysOfVacation: 28.0,
ShortSign: 'ADMIN',
State: 1,
SupportLevel: null,
Template1: false,
Template2: false,
Template3: false,
Template4: false,
VacationEntitlement: 30.0
},
SendMailToSelectedHelpdeskEmployees: false,
State: {
Description: 'Request noch nicht in Bearbeitung',
I3D: 1,
Icon: 'Qk02AwAAAAAAADYAAAAoAAAAEAAAABAAAAABABgAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAA////59S6pV8ApV8A59S6////////////////////////////////////////////59S6pmECw4Yiw4UipmIC59S6////////////////////////////////////////qWQExIkpz5g40Jg4xIkpqWQE59S6////////////////////////////////////59S6rGcGxoww055C051CxowxrGcG59S6////////////////////////////////////59S6r2sKyJE51aRP1aNPyJE5r2sK59S6////////////////////////////////////69m+sm8NzptG2Kpb2KpczppGsm8M69m+////////////////////////////////////69m+tXMR0J9Q27Fp27Fp0KBQtXMRtXMRtXMQtXMR69m+////////////////////////69m+uHcU06Va3rh337h33rh337l337l30qRauXgU69m+////////////////////////69m+vHwY4r+F4sCF4sCF2K1o2K5o4sCF2K1ovHwY7dzB////////////////////////v4Ab5caT5ceT2rNxv4Abv4Ab2rNy5caT2rNyv4Ab////////////////////////woQe6M6g3Ld7w4Qe7dzB7dzBw4Qe3Ld66M6gw4Uf////////////////////////xYgh69Sr3ryExYgh7dzB////7dzBxogi3ruDxogh////////////////////////yIsk4MCL7tm34MCMyIsk7dzB////7dzByIwk7dzB////////////////////////7dzByo4n4sOS792/4cOSyo0m7dzB////////////////////////////////////////7dzBzJAo48aX8eHG48aXzJAo////////////////////////////////////////////7dzBzJAozJAozJAo7dzB////////////',
IsDeactivated: null,
IsInternalCompanyBillingActive: null,
Name: 'offen',
Number: null,
ServiceBoardWebColor: null,
ServiceBoardWebIcon: null,
State: null
},
Substitutes: [],
SubstitutionEnd: null,
SubstitutionStart: null,
Type: {
Color: null,
Description: null,
I3D: 6,
IsDeactivated: null,
Name: 'Sonstige',
Number: 10,
ServiceBoardWebColor: null,
ServiceBoardWebIcon: null
}
},
ChangedBy: {
Availability: 0,
BranchI3D: 0,
DefaultStorageI3D: null,
Dispatcher: false,
DisplayText: 'Administrator, c-entron (ADMIN)',
Email: 'vertrieb@c-entron.de',
Employeenumber: 0,
FirstName: 'c-entron',
FullName: 'Administrator, c-entron (ADMIN)',
I3D: 22,
IsActive: true,
LastAvailabilityChange: null,
LastName: 'Administrator',
LeavingDate: DateTime,
Phone1: '0731-140399-0',
RemainingDaysOfVacation: 28.0,
ShortSign: 'ADMIN',
State: 1,
SupportLevel: null,
Template1: false,
Template2: false,
Template3: false,
Template4: false,
VacationEntitlement: 30.0
},
CreatedBy: {
Availability: 0,
BranchI3D: 0,
DefaultStorageI3D: null,
Dispatcher: false,
DisplayText: 'Administrator, c-entron (ADMIN)',
Email: 'vertrieb@c-entron.de',
Employeenumber: 0,
FirstName: 'c-entron',
FullName: 'Administrator, c-entron (ADMIN)',
I3D: 22,
IsActive: true,
LastAvailabilityChange: null,
LastName: 'Administrator',
LeavingDate: DateTime,
Phone1: '0731-140399-0',
RemainingDaysOfVacation: 28.0,
ShortSign: 'ADMIN',
State: 1,
SupportLevel: null,
Template1: false,
Template2: false,
Template3: false,
Template4: false,
VacationEntitlement: 30.0
},
Description: '-Klopf, klopf-Wer ist da ?-Po-Po wer ?-Pointe, aber ich muss Sie warnen, ich bin ziemlich entäuschend',
I3D: 4,
Name: 'Aktiver Task',
Recurrence: {
AppointmentEnd: null,
AppointmentStart: DateTime,
CountOfRecurrence: null,
DayInterval: 14,
EndTime: null,
EveryWeekday: false,
ExecuteAfterTime: null,
ExecuteDaysBefore: null,
I3D: 8,
NextExecutionDate: null,
NumberOfRecurrence: 53,
StartTime: DateTime
},
Status: 0
},
Error: null,
Message: null,
MessageCode: null,
Status: 0
}
@@ -0,0 +1,410 @@
{
Data: {
Action: {
ArticleWorkItemI3D: null,
Category: {
Description: null,
I3D: 6,
IsDeactivated: null,
IsPrivate: null,
Name: 'Hardware',
Number: 2,
ParentI3D: 1,
ServiceBoardWebColor: null,
ServiceBoardWebIcon: null,
SkillAssignments: [],
SubCategories: [
{
Description: null,
I3D: 7,
IsDeactivated: null,
IsPrivate: null,
Name: 'Raid',
Number: 1,
ParentI3D: 6,
ServiceBoardWebColor: null,
ServiceBoardWebIcon: null,
SkillAssignments: [],
SubCategories: []
},
{
Description: null,
I3D: 8,
IsDeactivated: null,
IsPrivate: null,
Name: 'Motherboard',
Number: 2,
ParentI3D: 6,
ServiceBoardWebColor: null,
ServiceBoardWebIcon: null,
SkillAssignments: [],
SubCategories: []
},
{
Description: null,
I3D: 52,
IsDeactivated: null,
IsPrivate: null,
Name: 'Festplatten',
Number: 3,
ParentI3D: 6,
ServiceBoardWebColor: null,
ServiceBoardWebIcon: null,
SkillAssignments: [],
SubCategories: []
},
{
Description: null,
I3D: 53,
IsDeactivated: null,
IsPrivate: null,
Name: 'Redundante Lüfter',
Number: 4,
ParentI3D: 6,
ServiceBoardWebColor: null,
ServiceBoardWebIcon: null,
SkillAssignments: [],
SubCategories: []
},
{
Description: null,
I3D: 61,
IsDeactivated: null,
IsPrivate: null,
Name: 'RAID System',
Number: 5,
ParentI3D: 6,
ServiceBoardWebColor: null,
ServiceBoardWebIcon: null,
SkillAssignments: [],
SubCategories: []
},
{
Description: null,
I3D: 62,
IsDeactivated: null,
IsPrivate: null,
Name: 'Lüfter',
Number: 6,
ParentI3D: 6,
ServiceBoardWebColor: null,
ServiceBoardWebIcon: null,
SkillAssignments: [],
SubCategories: []
}
]
},
Checklists: [],
ContactPersonOldReferenceI3D: null,
Contract: {
AdditionalText: '',
AutomaticExtensionAmountMonths: 0,
AutomaticExtensionFlag: false,
Begin: DateTime,
ContingentKind: 0,
ContingentOverbooking: 0,
ContractBegin: DateTime,
ContractEnd: DateTime,
ContractKind: {
Color: -16777195,
Description: 'Vertrag für Kontingent',
I3D: 3,
Name: 'Kontingent',
State: 1
},
CreateInvoiceOnCorporation: false,
CurrencyString: '€',
CustomerI3D: 10000,
DeductionContingentIntervalKind: 1,
DeductionContingentIntervalValue: 1,
DeductionIntervalKind: 1,
DeductionIntervalValue: 3,
Duration: null,
DurationKind: 2,
Editor: null,
ForeignContract: 0,
ForeignContractText: '',
I3D: 35,
IsClick: 0,
IsContingent: 1,
IsDisplayedOnWeb: false,
IsGeneralAgreement: 0,
IsHiddenInHelpdesk: false,
Name: '',
NetPriceFC: 0.0,
NetPriceFCComplete: 0.0,
Number: 14035,
PurchaseOrderNumber: '',
Receiver: 'c-entron software gmbh\r\nsdklfnfdjskfdsöfjsadkfjödöfjdsjafsöjdkfjösdajfsjdföjfjsdjfkjsadfjskdjfkjasdköal kalsjsvkjkldsjfkldösjkl jksdljvk jklsavj kjdslk jkldsj klödsj kljasdkl jkl jsckl jklasj klasj kljsö jksj dkljsdklj klsdj öklsdj klsdj lkjkasdj klasdj klsdj kljasfkl jöl\r\nFrau Franziska Franzen\r\nListstrasse 1\r\n89079 Ulm',
ReferenceNumber: '',
SLAPriority: 5,
State: 1,
TerminationDate: DateTime,
WebReportI3D: null
},
Customer: {
Adviser1: {
Availability: 0,
BranchI3D: 1,
DefaultStorageI3D: null,
Dispatcher: false,
DisplayText: 'Aiello, Angelina (AAI)',
Email: 'aiello@c-entron.de',
Employeenumber: 0,
FirstName: 'Angelina',
FullName: 'Aiello, Angelina (AAI)',
I3D: 38,
IsActive: true,
LastAvailabilityChange: null,
LastName: 'Aiello',
LeavingDate: DateTime,
Phone1: '',
RemainingDaysOfVacation: 0.0,
ShortSign: 'AAI',
State: 1,
SupportLevel: null,
Template1: false,
Template2: false,
Template3: false,
Template4: false,
VacationEntitlement: 0.0
},
Adviser2: {
Availability: 0,
BranchI3D: 0,
DefaultStorageI3D: null,
Dispatcher: false,
DisplayText: 'Hoffmann, Thomas (ABO)',
Email: 'lehnert@c-entron.de',
Employeenumber: 0,
FirstName: 'Thomas',
FullName: 'Hoffmann, Thomas (ABO)',
I3D: 27,
IsActive: true,
LastAvailabilityChange: null,
LastName: 'Hoffmann',
LeavingDate: DateTime,
Phone1: '0731-140399-333',
RemainingDaysOfVacation: 0.0,
ShortSign: 'ABO',
State: 1,
SupportLevel: null,
Template1: false,
Template2: false,
Template3: false,
Template4: false,
VacationEntitlement: 0.0
},
Adviser3: null,
Adviser4: null,
Adviser5: null,
Adviser6: null,
DefaultAddress: null,
EMail: '',
Fax: '',
HelpdeskClosingDontNotifyCustomer: null,
I3D: 10022,
IsCustomerKind1: false,
IsCustomerKind2: false,
IsCustomerKind3: false,
IsCustomerKind4: false,
IsCustomerKind5: false,
Locked: false,
Matchcode: '',
Name: 'Hubert Meier',
Phone: '',
RootDirI3D: 4359,
Status: 1,
Template1: false,
Template2: false,
Template3: false,
Template4: false,
Website: ''
},
Departments: [],
DueDateOffset: 0,
I3D: 10,
InternalNote: 'What sound does a programmer frog make?Regex, regex',
IsImportant: false,
IsOnlyInternalVisible: false,
MasterDataList: null,
Members: [
{
Employee: {
Availability: 0,
BranchI3D: 0,
DefaultStorageI3D: null,
Dispatcher: false,
DisplayText: 'Administrator, c-entron (ADMIN)',
Email: 'vertrieb@c-entron.de',
Employeenumber: 0,
FirstName: 'c-entron',
FullName: 'Administrator, c-entron (ADMIN)',
I3D: 22,
IsActive: true,
LastAvailabilityChange: null,
LastName: 'Administrator',
LeavingDate: DateTime,
Phone1: '0731-140399-0',
RemainingDaysOfVacation: 28.0,
ShortSign: 'ADMIN',
State: 1,
SupportLevel: null,
Template1: false,
Template2: false,
Template3: false,
Template4: false,
VacationEntitlement: 30.0
},
I3D: 16
}
],
PlannedDurationInHours: null,
Priority: {
Color: '#FF0000FF',
DueDateDelayInHours: 2.0,
EscalationSa: false,
EscalationSo: false,
Hour2: 4.0,
Hour3: 8.0,
I3D: 1,
IsDeactivated: null,
IsSLA: true,
Name: 'sofort (2 Stunden)',
Number: null,
OfficeHourFrom: DateTime,
OfficeHourTo: DateTime,
RegularWorkingDay: 2.0,
ServiceBoardWebColor: null,
ServiceBoardWebIcon: null,
State: 1
},
ResponsiblePerson: {
Availability: 0,
BranchI3D: 0,
DefaultStorageI3D: null,
Dispatcher: false,
DisplayText: 'Administrator, c-entron (ADMIN)',
Email: 'vertrieb@c-entron.de',
Employeenumber: 0,
FirstName: 'c-entron',
FullName: 'Administrator, c-entron (ADMIN)',
I3D: 22,
IsActive: true,
LastAvailabilityChange: null,
LastName: 'Administrator',
LeavingDate: DateTime,
Phone1: '0731-140399-0',
RemainingDaysOfVacation: 28.0,
ShortSign: 'ADMIN',
State: 1,
SupportLevel: null,
Template1: false,
Template2: false,
Template3: false,
Template4: false,
VacationEntitlement: 30.0
},
SendMailToSelectedHelpdeskEmployees: false,
State: {
Description: 'Request noch nicht in Bearbeitung',
I3D: 1,
Icon: 'Qk02AwAAAAAAADYAAAAoAAAAEAAAABAAAAABABgAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAA////59S6pV8ApV8A59S6////////////////////////////////////////////59S6pmECw4Yiw4UipmIC59S6////////////////////////////////////////qWQExIkpz5g40Jg4xIkpqWQE59S6////////////////////////////////////59S6rGcGxoww055C051CxowxrGcG59S6////////////////////////////////////59S6r2sKyJE51aRP1aNPyJE5r2sK59S6////////////////////////////////////69m+sm8NzptG2Kpb2KpczppGsm8M69m+////////////////////////////////////69m+tXMR0J9Q27Fp27Fp0KBQtXMRtXMRtXMQtXMR69m+////////////////////////69m+uHcU06Va3rh337h33rh337l337l30qRauXgU69m+////////////////////////69m+vHwY4r+F4sCF4sCF2K1o2K5o4sCF2K1ovHwY7dzB////////////////////////v4Ab5caT5ceT2rNxv4Abv4Ab2rNy5caT2rNyv4Ab////////////////////////woQe6M6g3Ld7w4Qe7dzB7dzBw4Qe3Ld66M6gw4Uf////////////////////////xYgh69Sr3ryExYgh7dzB////7dzBxogi3ruDxogh////////////////////////yIsk4MCL7tm34MCMyIsk7dzB////7dzByIwk7dzB////////////////////////7dzByo4n4sOS792/4cOSyo0m7dzB////////////////////////////////////////7dzBzJAo48aX8eHG48aXzJAo////////////////////////////////////////////7dzBzJAozJAozJAo7dzB////////////',
IsDeactivated: null,
IsInternalCompanyBillingActive: null,
Name: 'offen',
Number: null,
ServiceBoardWebColor: null,
ServiceBoardWebIcon: null,
State: null
},
Substitutes: [],
SubstitutionEnd: null,
SubstitutionStart: null,
Type: {
Color: null,
Description: null,
I3D: 6,
IsDeactivated: null,
Name: 'Sonstige',
Number: 10,
ServiceBoardWebColor: null,
ServiceBoardWebIcon: null
}
},
ChangedBy: {
Availability: 0,
BranchI3D: 0,
DefaultStorageI3D: null,
Dispatcher: false,
DisplayText: 'Administrator, c-entron (ADMIN)',
Email: 'vertrieb@c-entron.de',
Employeenumber: 0,
FirstName: 'c-entron',
FullName: 'Administrator, c-entron (ADMIN)',
I3D: 22,
IsActive: true,
LastAvailabilityChange: null,
LastName: 'Administrator',
LeavingDate: DateTime,
Phone1: '0731-140399-0',
RemainingDaysOfVacation: 28.0,
ShortSign: 'ADMIN',
State: 1,
SupportLevel: null,
Template1: false,
Template2: false,
Template3: false,
Template4: false,
VacationEntitlement: 30.0
},
CreatedBy: {
Availability: 0,
BranchI3D: 0,
DefaultStorageI3D: null,
Dispatcher: false,
DisplayText: 'Administrator, c-entron (ADMIN)',
Email: 'vertrieb@c-entron.de',
Employeenumber: 0,
FirstName: 'c-entron',
FullName: 'Administrator, c-entron (ADMIN)',
I3D: 22,
IsActive: true,
LastAvailabilityChange: null,
LastName: 'Administrator',
LeavingDate: DateTime,
Phone1: '0731-140399-0',
RemainingDaysOfVacation: 28.0,
ShortSign: 'ADMIN',
State: 1,
SupportLevel: null,
Template1: false,
Template2: false,
Template3: false,
Template4: false,
VacationEntitlement: 30.0
},
Description: '-Klopf, klopf-Wer ist da ?-Anne-Anne wer ?-An\'ne Schlüssel sollst du nächstes mal denken',
I3D: 5,
Name: 'Auch ein aktiver Task',
Recurrence: {
AppointmentEnd: null,
AppointmentStart: DateTime,
CountOfRecurrence: null,
DayInterval: 14,
EndTime: null,
EveryWeekday: false,
ExecuteAfterTime: null,
ExecuteDaysBefore: null,
I3D: 9,
NextExecutionDate: null,
NumberOfRecurrence: 80,
StartTime: DateTime
},
Status: 0
},
Error: null,
Message: null,
MessageCode: null,
Status: 0
}
@@ -0,0 +1,217 @@
using System;
using Centron.BusinessLogic;
using Centron.BusinessLogic.EmployeeArea;
using Centron.BusinessLogic.WebServices.TaskManager;
using Centron.Data.Entities.TaskManager;
using Centron.Data.WebServices.Administration.Employees;
using Centron.Data.WebServices.Sales.Customers;
using Centron.Data.WebServices.Sales.Support;
using Centron.Data.WebServices.TaskManager;
using Centron.Data.WebServices.TaskManager.Actions;
using Centron.Data.WebServices.TaskManager.Recurrence;
using Centron.Interfaces;
using Centron.Interfaces.BL;
using Centron.Tests.EndToEnd.Infrastructure;
using Xunit;
using Xunit.Abstractions;
namespace Centron.Tests.EndToEnd.Tests.TaskHelpdeskAction
{
/// <summary>
/// Ticket 168833: duplicating a task template onto another customer used to carry the source customer's
/// contact person over, so every generated ticket got a foreign contact. Covers both branches of the
/// customer check in TaskManagementHelpdeskActionHandler.
/// </summary>
public class TaskHelpdeskActionContactPersonTest : EndToEndTest
{
private const int CustomerI3D = 10022;
private const int UserI3D = 11;
public TaskHelpdeskActionContactPersonTest(ITestOutputHelper testOutputHelper) : base(testOutputHelper)
{
}
public override void Execute()
{
this.ContactOfTheOwnCustomerIsUsedForTheTicket();
this.ContactOfAnotherCustomerIsNotUsedForTheTicket();
}
private void ContactOfTheOwnCustomerIsUsedForTheTicket()
{
// Needs a customer with two contacts, so the configured contact differs from the default contact -
// otherwise the assertion could not tell the configured contact and the fallback apart.
var customerI3D = this.GetCustomerWithTwoContactsOnDefaultAddress();
var defaultTicketContactI3D = this.MakeSingleDefaultTicketContact(customerI3D);
var ownContactI3D = this.GetActiveContactOfCustomer(customerI3D, exceptContactI3D: defaultTicketContactI3D);
var taskI3D = this.CreateStartedTask("Task mit eigenem Ansprechpartner", customerI3D, ownContactI3D);
this.ExecuteTask(taskI3D);
this.AssertEveryTicketOfTaskUsesContact(taskI3D, ownContactI3D);
}
private void ContactOfAnotherCustomerIsNotUsedForTheTicket()
{
var defaultTicketContactI3D = this.MakeSingleDefaultTicketContact(CustomerI3D);
var foreignContactI3D = this.GetActiveContactOfAnotherCustomer(CustomerI3D);
var taskI3D = this.CreateStartedTask("Task mit fremdem Ansprechpartner", CustomerI3D, foreignContactI3D);
// The task itself must keep what was stored - the handler corrects the ticket, not the task data.
var storedContactI3D = this.Sql<int>(
$"SELECT ISNULL(ContactI3D, 0) FROM TaskManagementAction WHERE I3D = (SELECT TaskManagementActionI3D FROM TaskManagementTask WHERE I3D = {taskI3D})");
Assert.True(storedContactI3D == foreignContactI3D,
$"Expected the task to keep the stored contact {foreignContactI3D}, but it was changed to {storedContactI3D}.");
this.ExecuteTask(taskI3D);
this.AssertEveryTicketOfTaskUsesContact(taskI3D, defaultTicketContactI3D);
}
/// <summary>
/// Asserts that the task created at least one ticket and that every single one of them uses the expected
/// contact - checking only one ticket would miss a second one that kept the foreign contact.
/// </summary>
private void AssertEveryTicketOfTaskUsesContact(int taskI3D, int expectedContactI3D)
{
var ticketFilter = $@"
FROM hlpdsk_requests
WHERE CreatedFromObjectI3D = {taskI3D}
AND CreatedFromObjectKind = {(int)CentronObjectKindNumeric.TaskManagementClass}";
var ticketCount = this.Sql<int>($"SELECT COUNT(*) {ticketFilter}");
Assert.True(ticketCount > 0, $"No ticket was created for task {taskI3D}.");
var wrongContactCount = this.Sql<int>(
$"SELECT COUNT(*) {ticketFilter} AND ISNULL(AnsprechpartnerI3D, 0) <> {expectedContactI3D}");
Assert.True(wrongContactCount == 0,
$"{wrongContactCount} of {ticketCount} tickets of task {taskI3D} do not use the expected contact {expectedContactI3D}.");
}
/// <summary>
/// Marks one active contact of the customer's default address as the only ticket default contact and
/// returns it. This is the contact HelpdeskBL falls back to when a task carries no usable contact.
/// </summary>
private int MakeSingleDefaultTicketContact(int customerI3D)
{
var contactI3D = this.Sql<int>($@"
SELECT ISNULL(MIN(p.I3D), 0)
FROM Personen p
INNER JOIN Anschrif a ON a.I3D = p.AnschriftID
WHERE p.Status = 1 AND a.Status = 1 AND a.Kunde = {customerI3D} AND a.KundeDefault = 1");
Assert.True(contactI3D > 0,
$"The test database has no active contact on the default address of customer {customerI3D}.");
this.Sql($@"
UPDATE c SET c.DefaultTicket = 0
FROM AccountAddressContacts c
INNER JOIN Personen p ON p.I3D = c.OldReferenceI3D
INNER JOIN Anschrif a ON a.I3D = p.AnschriftID
WHERE a.Kunde = {customerI3D}");
this.Sql($"UPDATE AccountAddressContacts SET DefaultTicket = 1 WHERE OldReferenceI3D = {contactI3D}");
return contactI3D;
}
private int GetCustomerWithTwoContactsOnDefaultAddress()
{
var customerI3D = this.Sql<int>(@"
SELECT ISNULL(MIN(candidates.Kunde), 0)
FROM (
SELECT a.Kunde
FROM Personen p
INNER JOIN Anschrif a ON a.I3D = p.AnschriftID
WHERE p.Status = 1 AND a.Status = 1 AND a.Kunde IS NOT NULL AND a.KundeDefault = 1
GROUP BY a.Kunde
HAVING COUNT(*) >= 2
) candidates");
Assert.True(customerI3D > 0,
"The test database has no customer with two active contacts on its default address.");
return customerI3D;
}
private int CreateStartedTask(string name, int customerI3D, int contactPersonI3D)
{
using (var session = new BLSession())
{
var user = session.GetBL<AppUserBL>().GetAppUser(f => f.I3D == UserI3D);
var task = new TaskManagementTaskDTO
{
Name = name,
Description = name,
Status = ProjectStatus.Started,
Action = new TaskManagementHelpdeskActionDTO
{
Customer = new CustomerPreviewDTO { I3D = customerI3D },
ContactPersonOldReferenceI3D = contactPersonI3D,
Category = new HelpdeskCategoryDTO { I3D = 6 },
Priority = new HelpdeskPrioritiesDTO { I3D = 1 },
Type = new HelpdeskTypeDTO { I3D = 6 },
State = new HelpdeskStatusDTO { I3D = 1 },
ResponsiblePerson = new EmployeePreviewDTO { I3D = 22 }
},
Recurrence = new TaskManagementDailyRecurrenceDTO
{
AppointmentStart = new DateTime(2020, 2, 2, 13, 0, 0),
StartTime = new DateTime(2020, 2, 2, 13, 0, 0),
DayInterval = 14,
NumberOfRecurrence = 1
}
};
var savedTask = session.GetBL<TaskManagementTaskWebServiceBL>().SaveOrUpdateTask(task, user);
Assert.True(savedTask.Status == ResultStatus.Success, savedTask.Message);
return savedTask.Data.I3D;
}
}
private void ExecuteTask(int taskI3D)
{
using (var session = new BLSession())
{
var user = session.GetBL<AppUserBL>().GetAppUser(f => f.I3D == UserI3D);
var result = session.GetBL<TaskManagementTaskWebServiceBL>().ExecuteTask(taskI3D, user);
Assert.True(result.Status != ResultStatus.Error, result.Message);
}
}
private int GetActiveContactOfCustomer(int customerI3D, int exceptContactI3D)
{
var contactI3D = this.Sql<int>($@"
SELECT ISNULL(MIN(p.I3D), 0)
FROM Personen p
INNER JOIN Anschrif a ON a.I3D = p.AnschriftID
WHERE p.Status = 1 AND a.Status = 1 AND a.Kunde = {customerI3D} AND p.I3D <> {exceptContactI3D}");
Assert.True(contactI3D > 0,
$"The test database has no second active contact for customer {customerI3D}.");
return contactI3D;
}
private int GetActiveContactOfAnotherCustomer(int customerI3D)
{
var contactI3D = this.Sql<int>($@"
SELECT ISNULL(MIN(p.I3D), 0)
FROM Personen p
INNER JOIN Anschrif a ON a.I3D = p.AnschriftID
WHERE p.Status = 1 AND a.Status = 1 AND a.Kunde IS NOT NULL AND a.Kunde <> {customerI3D}");
Assert.True(contactI3D > 0, "The test database has no active contact of another customer.");
return contactI3D;
}
}
}
@@ -0,0 +1,354 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Centron.BusinessLogic;
using Centron.BusinessLogic.EmployeeArea;
using Centron.BusinessLogic.Sales.Receipts.DataAndResults;
using Centron.BusinessLogic.Sales.Support;
using Centron.BusinessLogic.TaskManager;
using Centron.BusinessLogic.WebServices.Sales.Receipts;
using Centron.BusinessLogic.WebServices.Sales.Support;
using Centron.BusinessLogic.WebServices.TaskManager;
using Centron.Data.Entities.Administration.Logins;
using Centron.Data.Entities.Sales.Support;
using Centron.Data.Entities.TaskManager;
using Centron.Data.WebServices.Administration.Employees;
using Centron.Data.WebServices.EmployeeArea;
using Centron.Data.WebServices.Sales.CustomerAssets.Contracts;
using Centron.Data.WebServices.Sales.Customers;
using Centron.Data.WebServices.Sales.Support;
using Centron.Data.WebServices.TaskManager;
using Centron.Data.WebServices.TaskManager.Actions;
using Centron.Data.WebServices.TaskManager.Recurrence;
using Centron.Interfaces;
using Centron.Interfaces.Administration.Environment;
using Centron.Tests.EndToEnd.Infrastructure;
using CentronSoftware.Centron.WebServices.Entities.Sales.Receipts.ContractLists;
using Xunit.Abstractions;
namespace Centron.Tests.EndToEnd.Tests.TaskHelpdeskAction
{
public class TaskHelpdeskActionTest : EndToEndTest
{
public const int CustomerI3D = 10022;
public const int FirstContractI3D = 10;
public const int SecondContractI3D = 35;
public const int ThirdContractI3D = 27;
public const int FirstSlaPrio = 1;
public const int SecondSlaPrio = 5;
public TaskHelpdeskActionTest(ITestOutputHelper testOutputHelper) : base(testOutputHelper)
{
this.Verifier.Settings.IgnoreProperty<TaskManagementTaskDTO>(f => f.ChangedDate);
this.Verifier.Settings.IgnoreProperty<TaskManagementTaskDTO>(f => f.CreatedDate);
}
public override void Execute()
{
this.PrepareDatabase();
var newTasksI3D = this.CreateTask();
this.ExecuteTasks();
//if you change the sla priority of a contract, tasks with this contract should be updated to use the new priority
this.ChangeContractPriority();
this.CheckTicketsFromTasksI3D(new[] { newTasksI3D.task0I3D, newTasksI3D.task1I3D,2, 3 }, true);
}
private void PrepareDatabase()
{
//make Markus Fischer the default contact person for tickets
this.Sql("UPDATE AccountAddressContacts SET DefaultTicket = 1 WHERE I3D = 3");
this.Sql("UPDATE TaskManagementAction SET ContactI3D = 4 WHERE I3D = 5");
//make a priority to sla
this.Sql($"UPDATE hlpdsk_prioritaeten SET IsSLA = 1 WHERE I3D ={FirstSlaPrio}");
// Change the priority of three contracts to SLA
this.Sql($"UPDATE dbo.VertragKopf SET SLAPriorityI3D = {SecondSlaPrio} WHERE I3D = {FirstContractI3D}");
this.Sql($"UPDATE dbo.VertragKopf SET SLAPriorityI3D = {SecondSlaPrio} WHERE I3D = {SecondContractI3D}");
this.Sql($"UPDATE dbo.VertragKopf SET SLAPriorityI3D = {SecondSlaPrio} WHERE I3D = {ThirdContractI3D}");
//Add contracts to existing tasks and set the Priority to sla
this.Sql($"UPDATE dbo.TaskManagementAction SET PriorityI3D = {SecondSlaPrio},ContractI3D = {FirstContractI3D} WHERE I3D = 8");
this.Sql($"UPDATE dbo.TaskManagementAction SET PriorityI3D = {SecondSlaPrio},ContractI3D = {ThirdContractI3D} WHERE I3D = 2");
// Make sure to execute task 2 only in the next 13 days
// Task 2 creates tickets on every sunday, so usually it creates 1 helpdesk
// But if the test is executed on a sunday, it will create 2 helpdesks, so the test is flaky
// Setting "ExecuteDaysBefore" to 13 makes the result consistent regardless of weekday
this.Sql($"UPDATE dbo.TaskManagementRecurrence SET ExecuteDaysBefore = 13 WHERE I3D = 2");
//Remove the end date, so the task generate tickets
this.Sql("UPDATE dbo.TaskManagementRecurrence SET EndTime = NULL,NumberOfRecurrence = 10 WHERE I3D = 2");
}
public (int task0I3D, int task1I3D) CreateTask()
{
using (var session = new BLSession())
{
var user = session.GetBL<AppUserBL>().GetAppUser(f => f.I3D == 11);
var taskStarted0 = new TaskManagementTaskDTO
{
Name = "Aktiver Task",
Description = "-Klopf, klopf" +
"-Wer ist da ?" +
"-Po" +
"-Po wer ?" +
"-Pointe, aber ich muss Sie warnen, ich bin ziemlich entäuschend",
Status = ProjectStatus.Started,
Action = new TaskManagementHelpdeskActionDTO()
{
Customer = new CustomerPreviewDTO
{
I3D = CustomerI3D
},
Category = new HelpdeskCategoryDTO()
{
I3D = 6
},
Contract = new ContractDTO()
{
I3D = SecondContractI3D
},
Priority = new HelpdeskPrioritiesDTO()
{
I3D = 1
},
Type = new HelpdeskTypeDTO()
{
I3D = 6
},
State = new HelpdeskStatusDTO()
{
I3D = 1
},
ResponsiblePerson = new EmployeePreviewDTO()
{
I3D = 22
},
Members = new List<TaskManagementActionMemberDTO>()
{
new TaskManagementActionMemberDTO
{
Employee = new EmployeeDTO() { I3D = 22 }
}
}
},
Recurrence = new TaskManagementDailyRecurrenceDTO
{
AppointmentStart = new DateTime(2020, 2, 2, 13, 0, 0),
StartTime = new DateTime(2020, 2, 2, 13, 0, 0),
DayInterval = 14,
NumberOfRecurrence = 53
},
};
var taskStarted1 = new TaskManagementTaskDTO
{
Name = "Auch ein aktiver Task",
Description = "-Klopf, klopf" +
"-Wer ist da ?" +
"-Anne" +
"-Anne wer ?" +
"-An'ne Schlüssel sollst du nächstes mal denken",
Status = ProjectStatus.Started,
Action = new TaskManagementHelpdeskActionDTO()
{
InternalNote = "What sound does a programmer frog make?"+
"Regex, regex",
Customer = new CustomerPreviewDTO
{
I3D = CustomerI3D
},
Category = new HelpdeskCategoryDTO()
{
I3D = 6
},
Contract = new ContractDTO()
{
I3D = SecondContractI3D
},
Priority = new HelpdeskPrioritiesDTO()
{
I3D = 1
},
Type = new HelpdeskTypeDTO()
{
I3D = 6
},
State = new HelpdeskStatusDTO()
{
I3D = 1
},
ResponsiblePerson = new EmployeePreviewDTO()
{
I3D = 22
},
Members = new List<TaskManagementActionMemberDTO>()
{
new TaskManagementActionMemberDTO
{
Employee = new EmployeeDTO() { I3D = 22 }
}
}
},
Recurrence = new TaskManagementDailyRecurrenceDTO
{
AppointmentStart = new DateTime(2020, 2, 2, 13, 0, 0),
StartTime = new DateTime(2020, 2, 2, 13, 0, 0),
DayInterval = 14,
NumberOfRecurrence = 80
},
};
var savedTask0 = session.GetBL<TaskManagementTaskWebServiceBL>().SaveOrUpdateTask(taskStarted0, user);
var savedTask1 = session.GetBL<TaskManagementTaskWebServiceBL>().SaveOrUpdateTask(taskStarted1, user);
this.Verifier.Verify("SavedTask0", savedTask0);
this.Verifier.Verify("SavedTask1", savedTask1);
return (savedTask0.Data.I3D, savedTask1.Data.I3D);
}
}
public void ExecuteTasks()
{
using (var session = new BLSession())
{
var user = session.GetBL<AppUserBL>().GetAppUser(f => f.I3D == 11);
var activeHelpdeskTasks = session.GetBL<TaskManagementTaskWebServiceBL>().GetTasks(1, int.MaxValue, new GetTasksFilter()
{
Type = TaskManagementActionType.Helpdesk,
Status = ProjectStatus.Started
});
foreach (var helpdeskTask in activeHelpdeskTasks.Data.Result)
{
var result = session.GetBL<TaskManagementTaskWebServiceBL>().ExecuteTask(helpdeskTask.I3D, user);
}
this.CheckTicketsFromTasksI3D(activeHelpdeskTasks.Data.Result.Select(f => f.I3D));
}
}
public void CheckTicketsFromTasksI3D(IEnumerable<int> tasksI3D, bool afterChanges = false)
{
using (var session = new BLSession())
{
var user = session.GetBL<AppUserBL>().GetAppUser(f => f.I3D == 11);
foreach (var helpdeskTaskI3D in tasksI3D)
{
//check if the tickets have been created
var closedState = session.GetBL<HelpdeskSettingsBL>().GetClosedHelpdeskState();
var tickets = session.GetBL<HelpdeskSearchWebServiceBL>().GetHelpdesksThroughPaging(
new HelpdeskFilter()
{
CreatedByTasks = true,
CreatedFromObjectKind = CentronObjectKindNumeric.TaskManagementClass,
CreatedFromObjectI3D = helpdeskTaskI3D
},
HelpdeskSort.Number,
true,
1,
Int32.MaxValue,
new LoggedInUser(user)
);
if (afterChanges == false)
this.Verifier.Verify("TicketFromTask" + helpdeskTaskI3D, tickets);
else
this.Verifier.Verify("TicketFromTaskAfterChanges" + helpdeskTaskI3D, tickets);
}
}
}
public void ChangeContractPriority()
{
using (var session = new BLSession())
{
var user = session.GetBL<AppUserBL>().GetAppUser(f => f.I3D == 11);
var contractNewVersion0 = session.GetBL<ReceiptWebServiceBL>().CreateNewVersion(
CentronObjectKindNumeric.ContractClass,
FirstContractI3D,
null,
null,
user,
new CreateNewVersionData
{
UpdateReceiptDate = false
});
var contract0 = (ReceiptContractDTO)contractNewVersion0.Data.Receipt;
contract0.SLAPriority = FirstSlaPrio;
var saveResult0 = new BLSession().GetBL<ReceiptWebServiceBL>().SaveReceipt(
contract0,
user,
new SaveReceiptData()
{
UpdatePrioritiesOfTaskTickets = true
},
false,
CreatedThroughApplication.CentronNet,
null);
var contractNewVersion1 = session.GetBL<ReceiptWebServiceBL>().CreateNewVersion(
CentronObjectKindNumeric.ContractClass,
SecondContractI3D,
null,
null,
user,
new CreateNewVersionData
{
UpdateReceiptDate = false
});
var contract1 = (ReceiptContractDTO)contractNewVersion1.Data.Receipt;
contract1.SLAPriority = FirstSlaPrio;
var saveResult1 = new BLSession().GetBL<ReceiptWebServiceBL>().SaveReceipt(
contract1,
user,
new SaveReceiptData()
{
UpdatePrioritiesOfTaskTickets = false
},
false,
CreatedThroughApplication.CentronNet,
null);
var contractNewVersion2 = session.GetBL<ReceiptWebServiceBL>().CreateNewVersion(
CentronObjectKindNumeric.ContractClass,
ThirdContractI3D,
null,
null,
user,
new CreateNewVersionData
{
UpdateReceiptDate = false
});
var contract2 = (ReceiptContractDTO)contractNewVersion2.Data.Receipt;
contract2.SLAPriority = FirstSlaPrio;
var saveResult2 = new BLSession().GetBL<ReceiptWebServiceBL>().SaveReceipt(
contract2,
user,
new SaveReceiptData()
{
IgnoreCallbacks = true
},
false,
CreatedThroughApplication.CentronNet,
null);
}
}
}
}
@@ -0,0 +1,165 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Centron.BusinessLogic;
using Centron.BusinessLogic.EmployeeArea;
using Centron.BusinessLogic.Mail.Factory;
using Centron.BusinessLogic.Sales.Support;
using Centron.BusinessLogic.WebServices.Administration.Settings.SettingGroups;
using Centron.BusinessLogic.WebServices.Sales.Support;
using Centron.BusinessLogic.WebServices.TaskManager;
using Centron.Data.Entities.Administration.Logins;
using Centron.Data.Entities.Sales.Support;
using Centron.Data.Entities.TaskManager;
using Centron.Data.WebServices.Administration.Employees;
using Centron.Data.WebServices.Sales.CustomerAssets.Contracts;
using Centron.Data.WebServices.Sales.Customers;
using Centron.Data.WebServices.Sales.Support;
using Centron.Data.WebServices.TaskManager;
using Centron.Data.WebServices.TaskManager.Actions;
using Centron.Data.WebServices.TaskManager.Recurrence;
using Centron.Interfaces;
using Centron.Interfaces.BL;
using Centron.Tests.EndToEnd.Infrastructure;
using Xunit;
using Xunit.Abstractions;
namespace Centron.Tests.EndToEnd.Tests.TaskHelpdeskAction
{
/// <summary>
/// Reproduction for Ticket #167558 (Raphael Frasch GmbH) — "Taskmanagement geht bringt Fehler".
///
/// A helpdesk task whose action has <see cref="TaskManagementHelpdeskActionDTO.SendMailToSelectedHelpdeskEmployees"/>
/// = true drives the second-save / ForwardHelpdesk path
/// (TaskManagementHelpdeskActionHandler.Execute -> HelpdeskWebServiceBL.ForwardHelpdesk ->
/// HelpdeskForwardBL.ForwardHelpdesk, which re-saves the just-created helpdesk).
///
/// Before the fix the customer saw:
/// "Batch update returned unexpected row count from update; actual row count: 0; expected: 1"
/// on UPDATE dbo.hlpdsk_requests ... WHERE ID = @p
/// and no ticket was created. Root cause: HelpdeskReportBL.GetReportForHelpdesk (reached via the
/// forward step) ran a throw-away transaction and rolled it back even when nested inside the task's
/// ambient transaction, dropping the just-created ticket.
///
/// The existing <see cref="TaskHelpdeskActionTest"/> never sets SendMailToSelectedHelpdeskEmployees,
/// so it does not exercise this path. This test does, and asserts the task creates a ticket and
/// reports success. It is a regression guard for that fix.
/// </summary>
public class TaskHelpdeskForwardActionTest : EndToEndTest
{
public const int CustomerI3D = 10022;
public const int ContractI3D = 35;
public const int ResponsibleEmployeeI3D = 22;
public const int UserI3D = 11;
public TaskHelpdeskForwardActionTest(ITestOutputHelper testOutputHelper) : base(testOutputHelper)
{
}
public override void Execute()
{
this.PrepareDatabase();
var taskI3D = this.CreateForwardingTask();
this.ExecuteAndAssertTicketCreated(taskI3D);
}
private void PrepareDatabase()
{
// The handler aborts/logs when the Task-Management sender address is empty, so set it.
using (var session = new BLSession())
{
var settings = session.GetBL<AppSettingsGroupWebServiceBL>().GetTaskManagementSettings();
settings.TaskManagementSenderAddress = "task-sender@example.com";
session.GetBL<AppSettingsGroupWebServiceBL>().UpdateTaskManagementSettings(settings);
}
// Make sure the employee we forward to has an e-mail address (used to build the internal mail).
this.Sql($"UPDATE dbo.Personal SET Email = 'editor@example.com' WHERE I3D = {ResponsibleEmployeeI3D} AND (Email IS NULL OR Email = '')");
}
private int CreateForwardingTask()
{
using (var session = new BLSession())
{
var user = session.GetBL<AppUserBL>().GetAppUser(f => f.I3D == UserI3D);
var task = new TaskManagementTaskDTO
{
Name = "Forwarding task (#167558 repro)",
Description = "Ticket should be created and forwarded to the selected employees.",
Status = ProjectStatus.Started,
Action = new TaskManagementHelpdeskActionDTO
{
Customer = new CustomerPreviewDTO { I3D = CustomerI3D },
Category = new HelpdeskCategoryDTO { I3D = 6 },
Contract = new ContractDTO { I3D = ContractI3D },
Priority = new HelpdeskPrioritiesDTO { I3D = 1 },
Type = new HelpdeskTypeDTO { I3D = 6 },
State = new HelpdeskStatusDTO { I3D = 1 },
ResponsiblePerson = new EmployeePreviewDTO { I3D = ResponsibleEmployeeI3D },
// This is the flag that triggers the ForwardHelpdesk second-save path.
SendMailToSelectedHelpdeskEmployees = true,
Members = new List<TaskManagementActionMemberDTO>
{
new TaskManagementActionMemberDTO
{
Employee = new EmployeePreviewDTO { I3D = ResponsibleEmployeeI3D }
}
}
},
Recurrence = new TaskManagementDailyRecurrenceDTO
{
AppointmentStart = new DateTime(2020, 2, 2, 13, 0, 0),
StartTime = new DateTime(2020, 2, 2, 13, 0, 0),
DayInterval = 14,
NumberOfRecurrence = 53
}
};
var saved = session.GetBL<TaskManagementTaskWebServiceBL>().SaveOrUpdateTask(task, user).ThrowIfError();
return saved.I3D;
}
}
private void ExecuteAndAssertTicketCreated(int taskI3D)
{
Result executeResult = null;
// Route the forward mail through the in-memory test transport so the test does not depend
// on a configured SMTP host.
TestMails.Start(() =>
{
using var session = new BLSession();
var user = session.GetBL<AppUserBL>().GetAppUser(f => f.I3D == UserI3D);
// Manual execution (single recurrence date), matching the Frasch case where even a
// manual run fails. Isolates a single create+forward cycle.
executeResult = session.GetBL<TaskManagementTaskWebServiceBL>().ExecuteTaskNow(taskI3D, user);
});
Assert.True(executeResult.Status == ResultStatus.Success,
$"ExecuteTask did not succeed (this reproduces #167558). Status={executeResult.Status}, Message={executeResult.Message}");
using (var session = new BLSession())
{
var user = session.GetBL<AppUserBL>().GetAppUser(f => f.I3D == UserI3D);
var tickets = session.GetBL<HelpdeskSearchWebServiceBL>().GetHelpdesksThroughPaging(
new HelpdeskFilter
{
CreatedByTasks = true,
CreatedFromObjectKind = CentronObjectKindNumeric.TaskManagementClass,
CreatedFromObjectI3D = taskI3D
},
HelpdeskSort.Number,
true,
1,
int.MaxValue,
new LoggedInUser(user)).ThrowIfError();
Assert.True(tickets.Result.Any(),
"No ticket was created for the forwarding task (this reproduces #167558: task runs but no ticket exists).");
}
}
}
}
@@ -0,0 +1,12 @@
{
Data: {
Count: 0,
CurrentPage: 1,
PageCount: 1,
Result: []
},
Error: null,
Message: null,
MessageCode: null,
Status: 0
}