CaritasVerbandDuerenJuelichEvBeWo/Export/DiamantExporter.cs - NEU (analog zur Familienhilfe)

This commit is contained in:
2026-04-16 13:22:24 +02:00
parent 0c44c21883
commit bee561beba
3 changed files with 663 additions and 0 deletions

View File

@@ -57,6 +57,8 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Export\CustomDataExporter.cs" />
<Compile Include="Export\DiamantExporter.cs" />
<Compile Include="Reporting\InvoiceCustomerReport.cs">
<SubType>Component</SubType>
</Compile>

View File

@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using BeWo.Service.Core;
using BeWo.Service.Plugins;
using BS.Shared.Core;
using BS.Shared.DataContracts;
using Utils = BS.Shared.Core.Utils;
namespace CaritasverbandDuerenJuelichEV.Export
{
public class SkmDataExporter : DataExporter
{
public override string CreateExportString(string pFileID, string[] pHeaderCaption, string[][] pContent)
{
if (pHeaderCaption != null && pHeaderCaption.Length > 0 && pHeaderCaption[0].Contains("Diamant"))
{
return DiamantExporter.CreateExportString(pHeaderCaption, pContent);
}
return base.CreateExportString(pFileID, pHeaderCaption, pContent);
}
public override QueryDC CreateQuery(QueryDC query)
{
var q = DiamantExporter.CreateQuery(query);
CreateBuchungsExportProtokollEintrag(q);
return q;
}
}
}

View File

@@ -0,0 +1,629 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Text;
using BeWo.Data.Access;
using BeWo.Data.Entities;
using BeWo.Report.DefaultReports;
using BeWo.Report.ReportObjects;
using BeWo.Service.Plugins;
using BS.Shared.DataContracts;
namespace CaritasverbandDuerenJuelichEV.Export
{
public class DiamantExporter
{
public static QueryDC CreateQuery(QueryDC query)
{
DateTime dt = DateTime.Now;
DateTime.TryParse(query.Parameter[0].Value.ToString(), out dt);
if (query.Title == "Diamant Export Stammdaten")
{
query.FileName = String.Format("005S{0:yyMM}.er2", dt);
query.QueryResult = GetDebitorenString(dt);
}
else
{
long? customerOid = null;
if (query.Parameter.Count > 1)
{
if (query.Parameter[1].Value != null)
{
long oid = 0;
if (Int64.TryParse(query.Parameter[1].Value.ToString(), out oid))
{
customerOid = oid;
}
}
}
if (query.Title == "Diamant Korrektur")
{
query.FileName = String.Format("005F{0:yyMM}.er2", dt);
query.QueryResult = GetKorrekturString(dt, customerOid);
}
else
{
query.FileName = String.Format("005F{0:yyyyMM}.er2", dt);
var dtAbgrenzungen = ExecuteQuery(GetAbgrenzungsLvrLwlSql(), dt);
Dictionary<string, string> oid2InvoiceNumbers;
query.QueryResult = GetAbrechnungenString(dt, customerOid, dtAbgrenzungen, out oid2InvoiceNumbers);
query.Attachments = CreateAttachmentFile(dt, dtAbgrenzungen, oid2InvoiceNumbers);
}
}
return query;
}
public static string CreateExportString(string[] pHeaderCaption, string[][] pContent)
{
if (pHeaderCaption != null && pHeaderCaption.Length > 1 && pHeaderCaption[0].Contains("Diamant"))
{
DateTime dt = DateTime.Now;
DateTime.TryParse(pHeaderCaption[1], out dt);
return GetAbrechnungenString(dt, null);
}
return null;
}
private static string GetKorrekturString(DateTime dt, long? customerOid)
{
var exports = DAOFactory.GenericDAO.GetAllActive<BuchungsExport>();
foreach (var exp in exports)
{
if (exp.Name == "Diamant Export Buchungssätze")
{
if (exp.Parameter.Contains(String.Format("Monat={0:dd.MM.yyyy}", dt)))
{
return GetKorrekturString(exp, customerOid);
}
}
}
return "";
}
private static string GetKorrekturString(BuchungsExport exp, long? customerOid)
{
StringBuilder sb = new StringBuilder();
var lines = exp.Export.Split(new string[] {"F,0,005,,,AR,"}, StringSplitOptions.RemoveEmptyEntries);
foreach (var line in lines)
{
if (!customerOid.HasValue)
{
sb.Append("F,0,005,,,AG,");
sb.Append(line);
}
else
{
var cust = DAOFactory.GenericDAO.LoadByID<Customer>(customerOid.Value);
String name = String.Format("{0}, {1}", cust.Person.LastName, cust.Person.FirstName);
String contains = String.Format("\"{0}\"", name);
if (line.Contains(contains))
{
sb.Append("F,0,005,,,AG,");
sb.Append(line);
}
}
}
return sb.ToString();
}
public static String GetDebitorenString(DateTime date)
{
var dt = ExecuteQuery(GetDebitorenSql(date), date);
StringBuilder sb = new StringBuilder();
foreach (DataRow row in dt.Rows)
{
if (sb.Length > 0)
{
sb.AppendLine();
}
for (int i = 0; i < row.ItemArray.Length; i++)
{
if (i > 0)
{
sb.Append(",");
}
var item = row[i];
bool quote = i == 4 || i == 5 || i == 6 || i > 24;
if (quote)
{
sb.Append('"');
sb.Append(item);
sb.Append('"');
}
else
{
sb.Append(item);
}
}
}
//Debitorenstamm
//S,0, D,200001,,"Bezeichnung1","Bezeichnung2",,,1200,,,,,,,,,,,,,,,,,"Name1","Name2", "Name3","Straße","PLZ","Ort"
return sb.ToString();
}
public static String GetAbrechnungenString(DateTime date, long? customerOid, DataTable dtAbgrenzungen = null)
{
Dictionary<string, string> _;
return GetAbrechnungenString(date, customerOid, dtAbgrenzungen, out _);
}
public static String GetAbrechnungenString(DateTime date, long? customerOid, DataTable dtAbgrenzungen, out Dictionary<string, string> oid2InvoiceNumbers)
{
StringBuilder sb = new StringBuilder();
// 1. Monatliche Abrechnungen
var s = GetMonatlicheAbrechnungString(date, customerOid);
if (!String.IsNullOrWhiteSpace(s))
{
sb.Append(s);
}
// 2. Spitzabrechnungen
s = GetSpitzabrechnungString(date, customerOid);
if (!String.IsNullOrWhiteSpace(s))
{
if (sb.Length > 0)
sb.AppendLine();
sb.Append(s);
}
// 3. Abgrenzungsbuchungen LVR/LWL
// DataTable wird von außen übergeben (aus CreateQuery), um doppelte SQL-Ausführung zu vermeiden.
// Falls nicht übergeben (z.B. aus CreateExportString), wird die SQL hier ausgeführt.
var abgrenzungenTable = dtAbgrenzungen ?? ExecuteQuery(GetAbgrenzungsLvrLwlSql(), date);
s = GetAbgrenzungsString(abgrenzungenTable, out oid2InvoiceNumbers);
if (!String.IsNullOrWhiteSpace(s))
{
if (sb.Length > 0)
sb.AppendLine();
sb.Append(s);
}
if (sb.Length > 0)
sb.AppendLine();
return sb.ToString();
}
public static String GetMonatlicheAbrechnungString(DateTime date, long? customerOid)
{
// Export enthält Rechnungsangaben Zeilen (Zeile F) und Kostenstellen/Rechnungspositionsangaben (Zeile K) in zwei Zeilen
// Kommen aus Export in einer Zeile und werden hier getrennt, aber nur falls die Rechnung mehrere Positionen hat
// Systemrechnungsnummern (ib.InvoiceNumber) werden direkt verwendet, kein Generator
var dt = ExecuteQuery(GetMonatlicheAbrechnungSql(), date);
return BuildDiamantLines(dt, null);
}
public static String GetSpitzabrechnungString(DateTime date, long? customerOid)
{
// Systemrechnungsnummern (ib.InvoiceNumber) werden direkt verwendet, kein Generator
var dt = ExecuteQuery(GetSpitzabrechnungSql(), date);
return BuildDiamantLines(dt, null);
}
public static String GetAbgrenzungsString(DataTable dt, out Dictionary<string, string> oid2GeneratedNumber)
{
// Abgrenzungsbuchungen für LVR/LWL haben keine Systemrechnungsnummern → pro Buchung (Zeile) eine Rechnungsnummer über den Generator vergeben
var generator = PluginLoader.FindClass<InvoiceNumberGenerator>();
var invoiceNumberDC = generator.LoadInvoiceNumber();
oid2GeneratedNumber = new Dictionary<string, string>();
int invoiceCount = 0;
if (invoiceNumberDC != null && invoiceNumberDC.Use)
{
foreach (DataRow row in dt.Rows)
{
string oid = row[0].ToString();
if (!oid2GeneratedNumber.ContainsKey(oid))
{
invoiceCount++;
// id = leer, da kein bestehender Bezug zur InvoiceNumber
oid2GeneratedNumber[oid] = generator.GetNextInvoiceNumber(invoiceCount, String.Empty);
}
}
}
string result = BuildDiamantLines(dt, oid2GeneratedNumber);
// Rechnungsnummern-Zähler in der DB hochschreiben
if (invoiceCount > 0)
{
generator.IncreaseInvoiceNumber(invoiceCount, new List<SettlementInvoice>());
}
return result;
}
// Gemeinsame Schleife für alle Buchungsarten im Diamant-Format.
// Spalte 0: OID (zur Deduplizierung bei mehreren Positionen pro Rechnung).
// Spalten 1-33: F-Zeile (Rechnungskopf). Ab Spalte 34 (stopIdx): K-Zeile (Kostenstelle/Position).
// Wenn oid2GeneratedNumber != null und die OID enthält, wird Spalte 10 (Rechnungsnummer) durch die generierte Nummer ersetzt. Andernfalls wird der DB-Wert verwendet.
private static String BuildDiamantLines(DataTable dt, Dictionary<string, string> oid2GeneratedNumber)
{
const int startIdx = 1;
const int stopIdx = 34; // ab da beginnen Angaben zu Kostenstelle → zweite Zeile
StringBuilder sb = new StringBuilder();
Dictionary<string, DataRow> oid2Row = new Dictionary<string, DataRow>();
foreach (DataRow row in dt.Rows)
{
string oid = row[0].ToString();
if (!oid2Row.ContainsKey(oid))
{
// Erste Zeile für diese OID: F-Zeile + erste K-Zeile
oid2Row.Add(oid, row);
if (sb.Length > 0)
sb.AppendLine();
for (int i = startIdx; i < row.ItemArray.Length; i++)
{
var item = row[i];
if (i == 10 && oid2GeneratedNumber != null && oid2GeneratedNumber.ContainsKey(oid))
{
// Generierte Rechnungsnummer statt DB-Wert
sb.Append(oid2GeneratedNumber[oid]);
sb.Append(";");
}
else if (i == 15 || i == 42)
{
sb.Append(String.Format("{0}", item.ToString().Replace(",", ".")));
sb.Append(";");
}
else if (i == 16)
{
sb.Append('"');
sb.Append(item);
sb.Append('"');
}
else if (i == stopIdx)
{
sb.AppendLine();
sb.Append(item);
sb.Append(";");
}
else if (i == 12 || i == 44)
{
string konto = SetKonto(item);
sb.Append(String.Format("{0}", konto));
sb.Append(";");
}
else
{
sb.Append(item);
sb.Append(";");
}
}
}
else
{
// Weitere Zeile für dieselbe OID: nur zusätzliche K-Zeile ausgeben
if (sb.Length > 0)
sb.AppendLine();
for (int i = startIdx; i < row.ItemArray.Length; i++)
{
var item = row[i];
// F-Zeilen-Spalten überspringen, nur Kostenstellen ausgeben
if (i == stopIdx)
{
sb.Append(item);
sb.Append(";");
}
else if (i == 42)
{
sb.Append(String.Format("{0}", item.ToString().Replace(",", ".")));
sb.Append(";");
}
else if (i == 44)
{
string konto = SetKonto(item);
sb.Append(String.Format("{0}", konto));
sb.Append(";");
}
else if (i > stopIdx)
{
sb.Append(item);
sb.Append(";");
}
}
}
}
return sb.ToString();
}
private static string SetKonto(object item)
{
string konto = "84190"; // eigentlich alles außer Selbstzahler
if (item != null)
{
if (item.ToString().ToLower().Contains("selbstzahler"))
{
konto = "84191";
}
}
return konto;
}
private static String GetDebitorenSql(DateTime date)
{
String sql = @"
SELECT
distinct
'S',
'0',
'D',
c.DebitorNumber,
CONCAT(p.`LastName`, ' ', p.`FirstName`),
CONCAT(p.`LastName`, ' ', p.`FirstName`),
cb2sc.CustomerRefenrenceNumber,
null,
null,
null,
null,
null,
null,
null,
'D',
'30 Tage netto',
null,
null,
null,
null,
null,
null,
null,
null,
null,
p.`FirstName`,
p.`LastName`,
'',
'',
a.Street,
a.PostalCode,
a.Town
FROM `customer` c
INNER JOIN `person` p ON p.`Oid` = c.`PersonOid`
INNER JOIN supportconcept sc on sc.CustomerOID = c.Oid
INNER JOIN costbearer2supportconcept cb2sc on cb2sc.SupportConceptOID = sc.Oid
LEFT JOIN Address a on p.addressOid = a.oid
WHERE c.isactive=1
order by p.`LastName`, p.FirstName
";
return sql;
}
private static String GetMonatlicheAbrechnungSql()
{
String sql = @"
SELECT
CONCAT(cb2sc.Oid, ib.InvoiceNumber, Round(sip.Claim, 0)) AS OID,
'F',
'0',
'99',
null,
null,
'AR',
':Abrechnungsmonat',
':Periode',
null,
ib.InvoiceNumber,
c.DebitorNumber,
CONCAT(ii.ItemDescription, ' ', org.Name),
null,
null,
Round(sip.Claim, 2) AS 'Betrag',
CONCAT('Re. ', p.`LastName`, ', ', p.`FirstName`) as 'Verwendung',
null, null, null, 'EUR',
null, null, null, '14', null, null, null, null, null, null, null, null, null,
'K', 0, c.CostCenter, null, 'V', null, null, null, Round(ii.AmountTotal * -1, 2), null, CONCAT(ii.ItemDescription, ' ', org.Name), null, null, null, null, null, null
FROM
person p
INNER JOIN customer c on c.personoid = p.oid
INNER JOIN supportconcept sc on sc.customeroid = c.oid
INNER JOIN costbearer2supportconcept cb2sc on cb2sc.supportconceptoid = sc.oid
INNER JOIN costbearer cb on cb2sc.costbeareroid = cb.oid
INNER JOIN organisation org on org.costbeareroid = cb.oid
INNER JOIN invoicebase ib on ib.costbearer2supportconceptoid = cb2sc.oid
INNER JOIN serviceinvoice si on si.invoicebaseoid = ib.oid
INNER JOIN serviceinvoiceperiod sip on sip.serviceinvoiceoid = si.oid
INNER JOIN invoiceitem ii on ii.ServiceInvoicePeriodOid = sip.oid
WHERE c.`IsActive` = 1 AND sc.`IsActive` = 1 and ib.isactive = 1 and ib.type <> 1
and ib.`AccountingPeriodEnd` >= ':Monat_Start' AND ib.`AccountingPeriodStart` < ':Monat_End'
ORDER BY ib.invoicenumber
";
return sql;
}
private static String GetSpitzabrechnungSql()
{
String sql = @"
SELECT
CONCAT(cb2sc.Oid, ib.InvoiceNumber) AS OID,
'F',
'0',
'99',
null,
null,
'AR',
':Abrechnungsmonat',
':Periode',
null,
ib.InvoiceNumber,
c.DebitorNumber,
CONCAT('Spitzabrechnung ', org.Name),
null,
null,
Round(si.Claim, 2) AS 'Betrag',
CONCAT('Re. ', p.`LastName`, ', ', p.`FirstName`) as 'Verwendung',
null, null, null, 'EUR',
null, null, null, '14', null, null, null, null, null, null, null, null, null,
'K', 0, c.CostCenter, null, 'V', null, null, null, Round(si.Claim * -1, 2), null, CONCAT('Spitzabrechnung ', org.Name), null, null, null, null, null, null
FROM person p
INNER JOIN customer c on c.personoid = p.oid
INNER JOIN supportconcept sc on sc.customeroid = c.oid
INNER JOIN costbearer2supportconcept cb2sc on cb2sc.supportconceptoid = sc.oid
INNER JOIN costbearer cb on cb2sc.costbeareroid = cb.oid
INNER JOIN organisation org on org.costbeareroid = cb.oid
INNER JOIN invoicebase ib on ib.costbearer2supportconceptoid = cb2sc.oid
INNER JOIN settlementinvoice si on si.invoicebaseoid = ib.oid
WHERE c.`IsActive` = 1 AND sc.`IsActive` = 1 and ib.isactive = 1
AND ib.`AccountingPeriodEnd` >= ':Monat_Start' AND ib.`AccountingPeriodStart` < ':Monat_End'
ORDER BY ib.invoicenumber
";
return sql;
}
private static String GetAbgrenzungsLvrLwlSql()
{
String sql = @"
SELECT
CAST(stunden.cb2sc_oid AS CHAR) AS OID,
'F',
'0',
'99',
null,
null,
'AR',
':Abrechnungsmonat',
':Periode',
null,
null AS InvoiceNumber,
stunden.DebitorNumber,
CONCAT('Abgrenzung ', stunden.OrgName),
null,
null,
ROUND(stunden.Stunden * stunden.Stundensatz, 2) AS Betrag,
CONCAT('Abgr. ', stunden.LastName, ', ', stunden.FirstName) as Verwendung,
null, null, null, 'EUR',
null, null, null, '14', null, null, null, null, null, null, null, null, null,
'K', 0, stunden.CostCenter, null, 'V', null, null, null,
ROUND(stunden.Stunden * stunden.Stundensatz * -1, 2),
null,
CONCAT('Abgrenzung ', stunden.OrgName),
null, null, null, null, null, null
FROM (
SELECT
cb2sc.Oid AS cb2sc_oid,
c.DebitorNumber,
org.Name AS OrgName,
p.LastName,
p.FirstName,
SUM(sr.roundedduration / IF(sr.GroupEmployeeCount IS NULL, 1, sr.GroupEmployeeCount)) / 60 AS Stunden,
(SELECT crp.CostRateValue
FROM costrateperiod crp
WHERE crp.ObjectTid = 22 AND crp.CostRateType = 0 AND crp.ObjectOid = cb2sc.CostBearerOid
AND (crp.EndDate IS NULL OR crp.EndDate > ':Monat_Start')
ORDER BY IF(crp.EndDate IS NULL, MAKEDATE(9999,365), crp.EndDate) LIMIT 1) AS Stundensatz,
c.CostCenter
FROM supportconcept sc
INNER JOIN costbearer2supportconcept cb2sc ON sc.Oid = cb2sc.SupportConceptOid
INNER JOIN costbearer cb ON cb2sc.CostBearerOid = cb.Oid
INNER JOIN organisation org ON org.CostBearerOid = cb.Oid
INNER JOIN customer c ON sc.CustomerOid = c.Oid
INNER JOIN person p ON c.PersonOid = p.Oid
INNER JOIN servicerecord sr ON sr.CostBearer2SupportConceptOid = cb2sc.Oid
INNER JOIN servicedescription sd ON sr.ServiceDescriptionOid = sd.Oid
INNER JOIN servicecategory scat ON sd.ServiceCategoryOid = scat.Oid
WHERE c.IsActive = 1 AND sc.IsActive = 1
AND (org.Name = 'LVR' OR org.Name = 'LWL')
AND sr.StartDate >= ':Monat_Start' AND sr.StartDate < ':Monat_End'
AND scat.Billable = 1
GROUP BY cb2sc.Oid, c.DebitorNumber, org.Name, p.LastName, p.FirstName, c.CostCenter
HAVING SUM(sr.roundedduration) > 0
) AS stunden
ORDER BY stunden.LastName, stunden.FirstName
";
return sql;
}
private static List<FileAttachmentDC> CreateAttachmentFile(DateTime date, DataTable dtAbgrenzungen, Dictionary<string, string> oid2GeneratedNumber)
{
try
{
// Spalten aus der bereits ausgeführten Abgrenzungs-DataTable verwenden:
// 0 = OID, 11 = DebitorNumber, 12 = 'Abgrenzung OrgName', 15 = Betrag, 16 = 'Abgr. Nachname, Vorname'
DataTable table = new DataTable();
string[] spalten = { "Rechnungsnr.", "Empfänger", "Debitornr.", "Erlöskonto", "Kostenstelle", "Verwendung", "Betrag" };
foreach (var spalte in spalten)
table.Columns.Add(spalte, typeof(string));
foreach (DataRow booking in dtAbgrenzungen.Rows)
{
string oid = booking[0].ToString();
string invoiceNumber = String.Empty;
if (oid2GeneratedNumber != null && oid2GeneratedNumber.ContainsKey(oid))
invoiceNumber = oid2GeneratedNumber[oid];
string erloeskonto = SetKonto(booking[12]);
DataRow row = table.NewRow();
row[0] = invoiceNumber;
row[1] = booking[12].ToString().Replace("Abgrenzung ", "");
row[2] = booking[11];
row[3] = erloeskonto;
row[4] = booking[36]; // c.CostCenter
row[5] = booking[16].ToString().Replace("Abgr. ", "");
row[6] = String.Format("{0:0.00}", booking[15]);
table.Rows.Add(row);
}
var queryReportObject = QueryRO.Create(new Query() { Title = "Export" }, table);
queryReportObject.Name = String.Format("Abgrenzungsbuchungen {0:MMMM yyyy}", date);
var queryReport = new Bericht();
queryReport.SetReportDataSource(queryReportObject);
queryReport.CreateDocument();
var pdf = new FileAttachmentDC();
pdf.FileName = String.Format("Abgrenzungsbuchungen{0:MMyyyy}.pdf", date);
using (var ms = new MemoryStream())
{
queryReport.ExportToPdf(ms);
pdf.BinaryData = ms.ToArray();
}
return new List<FileAttachmentDC> { pdf };
}
catch (Exception)
{
return null;
}
}
public static DataTable ExecuteQuery(String sql, DateTime dt)
{
var newsql = sql;
newsql = newsql.Replace(":PeriodeSlash", String.Format("{0:MM/yyyy}", dt));
newsql = newsql.Replace(":Periode", String.Format("{0:MMyyyy}", dt));
newsql = newsql.Replace(":Abrechnungsmonat", String.Format("{0:ddMMyyyy}", dt));
newsql = newsql.Replace(":Monat_Start", String.Format("{0:yyyy-MM}-01", dt));
dt = dt.AddMonths(1);
newsql = newsql.Replace(":Monat_End", String.Format("{0:yyyy-MM}-01", dt));
return DAOFactory.AdoDAO.ExecuteQuery(newsql).Tables[0];
}
}
}