feat(import): RPRO3-Importer um 6 Feature-Tabellen erweitern
becken→Gehege-Reinigung, herktier→Erwerb, abgeben/abstat→Reservierung, getback→Rücknahmen, nachfrage→Warteliste, ausz→Ausstellungen. Deterministische GUIDs, idempotenter Re-Import. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -213,7 +213,7 @@ namespace GerbilManagerWebAPI.Import.Rpro3
|
||||
|
||||
// stamm_tb (eigene Tiere)
|
||||
Query(conn,
|
||||
"SELECT id,_NAME,_SEX,_BIRTH,_HERKUNFT,_ZB,_STATUS,_FEHLER,_KASTRAT_DATE FROM stamm_tb",
|
||||
"SELECT id,_NAME,_SEX,_BIRTH,_HERKUNFT,_ZB,_STATUS,_FEHLER,_KASTRAT_DATE,_BECKEN FROM stamm_tb",
|
||||
(r, ord) =>
|
||||
{
|
||||
var sid = IntOrNull(r, ord(r, "id"));
|
||||
@@ -221,6 +221,8 @@ namespace GerbilManagerWebAPI.Import.Rpro3
|
||||
var rid = sid.Value.ToString();
|
||||
var name = Str(r, ord(r, "_NAME"));
|
||||
stammName[sid.Value] = name;
|
||||
var beckenId = IntOrNull(r, ord(r, "_BECKEN"));
|
||||
if (beckenId is > 0) data.StammBecken[rid] = beckenId.Value;
|
||||
var (farbe, fcode) = ColorFor(rid);
|
||||
baum.TryGetValue(rid, out var par);
|
||||
var herkId = IntOrNull(r, ord(r, "_HERKUNFT"));
|
||||
@@ -406,9 +408,133 @@ namespace GerbilManagerWebAPI.Import.Rpro3
|
||||
if (set.FileNames.Count > 0) data.Photos[set.Tid] = set;
|
||||
});
|
||||
|
||||
ParseFeatureTables(conn, data);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/// <summary>Parst die Feature-Tabellen (becken/herktier/abgeben/abstat/getback/nachfrage/ausz).
|
||||
/// Jede Tabelle ist optional — fehlt sie in einem älteren Backup, wird sie still übersprungen.</summary>
|
||||
private void ParseFeatureTables(SqliteConnection conn, Rpro3Data data)
|
||||
{
|
||||
// becken_tb: Gehege.
|
||||
QueryOptional(conn, "SELECT id,_BEZ,_SIZE,_MENGE,_CLEANED,_CYCLUS FROM becken_tb", (r, ord) =>
|
||||
{
|
||||
var id = IntOrNull(r, ord(r, "id"));
|
||||
if (id is null) return;
|
||||
data.Becken.Add(new Rpro3Becken
|
||||
{
|
||||
Id = id.Value,
|
||||
Name = Str(r, ord(r, "_BEZ")),
|
||||
Size = Str(r, ord(r, "_SIZE")),
|
||||
Capacity = IntOrNull(r, ord(r, "_MENGE")),
|
||||
LastCleaned = JdnToDate(Real(r, ord(r, "_CLEANED"))),
|
||||
CycleDays = IntOrNull(r, ord(r, "_CYCLUS")),
|
||||
});
|
||||
});
|
||||
|
||||
// herktier_tb: Erwerb.
|
||||
QueryOptional(conn, "SELECT id,_TID,_DATE,_PRICE,_BEM FROM herktier_tb", (r, ord) =>
|
||||
{
|
||||
var id = IntOrNull(r, ord(r, "id"));
|
||||
if (id is null) return;
|
||||
data.Acquisitions.Add(new Rpro3Acquisition
|
||||
{
|
||||
Id = id.Value,
|
||||
Tid = Str(r, ord(r, "_TID")),
|
||||
Date = JdnToDate(Real(r, ord(r, "_DATE"))),
|
||||
Price = DecimalOrNull(r, ord(r, "_PRICE")),
|
||||
Note = Str(r, ord(r, "_BEM")),
|
||||
});
|
||||
});
|
||||
|
||||
// abgeben_tb: Reservierung/Vormerkung.
|
||||
QueryOptional(conn, "SELECT tid,_RES,_TERMIN,_ABN,_BEM FROM abgeben_tb", (r, ord) =>
|
||||
{
|
||||
var tid = Str(r, ord(r, "tid"));
|
||||
if (string.IsNullOrWhiteSpace(tid)) return;
|
||||
data.Abgeben.Add(new Rpro3Abgeben
|
||||
{
|
||||
Tid = tid,
|
||||
Reserved = (IntOrNull(r, ord(r, "_RES")) ?? 0) != 0,
|
||||
Appointment = JdnToDate(Real(r, ord(r, "_TERMIN"))),
|
||||
AbnId = IntOrNull(r, ord(r, "_ABN")),
|
||||
Note = Str(r, ord(r, "_BEM")),
|
||||
});
|
||||
});
|
||||
|
||||
// abstat_tb: Abgabe-Abschluss.
|
||||
QueryOptional(conn, "SELECT tid,_IDABN,_AM,_PRICE,_BEM FROM abstat_tb", (r, ord) =>
|
||||
{
|
||||
var tid = Str(r, ord(r, "tid"));
|
||||
if (string.IsNullOrWhiteSpace(tid)) return;
|
||||
data.Abstat.Add(new Rpro3Abstat
|
||||
{
|
||||
Tid = tid,
|
||||
AbnId = IntOrNull(r, ord(r, "_IDABN")),
|
||||
HandedOver = JdnToDate(Real(r, ord(r, "_AM"))),
|
||||
Price = DecimalOrNull(r, ord(r, "_PRICE")),
|
||||
Note = Str(r, ord(r, "_BEM")),
|
||||
});
|
||||
});
|
||||
|
||||
// getback_tb: Rücknahme.
|
||||
QueryOptional(conn, "SELECT id,_TID,_ZAM,_ZPREIS,_AM,_PREIS,_ABN,_BEM FROM getback_tb", (r, ord) =>
|
||||
{
|
||||
var id = IntOrNull(r, ord(r, "id"));
|
||||
if (id is null) return;
|
||||
data.Getback.Add(new Rpro3Getback
|
||||
{
|
||||
Id = id.Value,
|
||||
Tid = Str(r, ord(r, "_TID")),
|
||||
ReturnDate = JdnToDate(Real(r, ord(r, "_ZAM"))),
|
||||
ReturnPrice = DecimalOrNull(r, ord(r, "_ZPREIS")),
|
||||
OriginalSaleDate = JdnToDate(Real(r, ord(r, "_AM"))),
|
||||
OriginalPrice = DecimalOrNull(r, ord(r, "_PREIS")),
|
||||
AbnId = IntOrNull(r, ord(r, "_ABN")),
|
||||
Note = Str(r, ord(r, "_BEM")),
|
||||
});
|
||||
});
|
||||
|
||||
// nachfrage_tb: Warteliste. _VOM ist das Anfragedatum (_DATE ist hier i. d. R. 0).
|
||||
// Hat keine eigene _BEM-Spalte; _CODE (z. B. "egal") dient als Zusatznotiz.
|
||||
QueryOptional(conn, "SELECT id,_COLOR,_SEX,_ABN,_VOM,_DATE,_STATUS,_CODE FROM nachfrage_tb", (r, ord) =>
|
||||
{
|
||||
var id = IntOrNull(r, ord(r, "id"));
|
||||
if (id is null) return;
|
||||
var code = Str(r, ord(r, "_CODE"));
|
||||
data.Nachfrage.Add(new Rpro3Nachfrage
|
||||
{
|
||||
Id = id.Value,
|
||||
WishColor = Str(r, ord(r, "_COLOR")),
|
||||
WishGender = ParseGender(Str(r, ord(r, "_SEX"))),
|
||||
AbnId = IntOrNull(r, ord(r, "_ABN")),
|
||||
RequestedAt = JdnToDate(Real(r, ord(r, "_VOM"))) ?? JdnToDate(Real(r, ord(r, "_DATE"))),
|
||||
Status = Str(r, ord(r, "_STATUS")),
|
||||
Note = string.IsNullOrWhiteSpace(code) || code == "egal" ? null : code,
|
||||
});
|
||||
});
|
||||
|
||||
// ausz_tb: Ausstellungsergebnisse (in vielen Backups leer — robust gegen 0 Zeilen).
|
||||
QueryOptional(conn, "SELECT id,_TID,_VERANSTALTUNG,_DATE,_ORT,_PLATZ,_AUSZ,_JUROR,_BEM FROM ausz_tb", (r, ord) =>
|
||||
{
|
||||
var id = IntOrNull(r, ord(r, "id"));
|
||||
if (id is null) return;
|
||||
data.Ausstellungen.Add(new Rpro3Ausstellung
|
||||
{
|
||||
Id = id.Value,
|
||||
Tid = Str(r, ord(r, "_TID")),
|
||||
EventName = Str(r, ord(r, "_VERANSTALTUNG")),
|
||||
Date = JdnToDate(Real(r, ord(r, "_DATE"))),
|
||||
Place = Str(r, ord(r, "_ORT")),
|
||||
Placement = Str(r, ord(r, "_PLATZ")),
|
||||
Award = Str(r, ord(r, "_AUSZ")),
|
||||
Juror = Str(r, ord(r, "_JUROR")),
|
||||
Note = Str(r, ord(r, "_BEM")),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private static Dictionary<string, (string farbe, string fcode)> ReadColors(SqliteConnection conn, string table)
|
||||
{
|
||||
var dict = new Dictionary<string, (string, string)>();
|
||||
@@ -468,6 +594,16 @@ namespace GerbilManagerWebAPI.Import.Rpro3
|
||||
onRow(r, ord);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Wie <see cref="Query"/>, ignoriert aber fehlende Tabellen/Spalten
|
||||
/// (ältere Backups ohne die Feature-Tabellen). SQLite-Fehler werden geschluckt.</summary>
|
||||
private static void QueryOptional(
|
||||
SqliteConnection conn, string sql,
|
||||
Action<SqliteDataReader, Func<SqliteDataReader, string, int>> onRow)
|
||||
{
|
||||
try { Query(conn, sql, onRow); }
|
||||
catch (SqliteException) { /* Tabelle/Spalte fehlt in diesem Backup → überspringen */ }
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class Rpro3FormatException : Exception
|
||||
|
||||
Reference in New Issue
Block a user