feat(erp): Writeback schreibt abgeleitete Beleg-Felder mit, _SV_DELIVERY raus
Beim Mengen-Rückschreiben werden jetzt auch die von der ERPframe-Engine mitgeführten abgeleiteten Felder gesetzt, damit der Beleg konsistent bleibt (Lagerbestand bucht das Lager weiterhin manuell — keine Lagerbewegung): - Belegzeile: StatUmsatz, StatKosten (linear aus Alt-Stand skaliert), Lagerverteilung (XML neu), row_update_time. VPEMenge NICHT (berechnete Spalte → Fehler 271). - Belegkopf: zusätzlich StatKosten, kalk (Kalkulations-XML), Druckkennzeichen, row_update_time. - Entfernt: mark_delivered (_SV_DELIVERY_DELIVERED_AT/_SV_DELIVERY_STATE) — wird nicht mehr benötigt. Alle geschriebenen Spalten gegen sys.computed_columns verifiziert. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -11,7 +11,6 @@
|
||||
//! 2. Je Belegzeile die `Menge` auf die ausgelieferte Menge setzen.
|
||||
//! 3. Gutschrift-Zeile (`GUTSCHRIFT10`) anlegen/aktualisieren.
|
||||
//! 4. Belegsummen neu berechnen (`Σ Einzelpreis × Menge`, `Σ Brutto × Menge`).
|
||||
//! 5. `_SV_DELIVERY_DELIVERED_AT` + `_SV_DELIVERY_STATE='geliefert'`.
|
||||
//!
|
||||
//! Hinweis: Das Setzen der Menge per Raw-`UPDATE` umgeht die ERPframe-Engine,
|
||||
//! erzeugt also KEINE Lagerbuchung. Das ist gewollt — das Lager verbucht den
|
||||
@ -125,19 +124,66 @@ impl MssqlErpDeliveryWriteback {
|
||||
})
|
||||
}
|
||||
|
||||
/// Setzt die Menge einer Belegzeile (absolut, idempotent) auf die
|
||||
/// tatsächlich ausgelieferte Menge.
|
||||
/// TEST: Setzt die Menge einer Belegzeile auf die ausgelieferte Menge UND
|
||||
/// die von der ERPframe-Engine mitgeführten abgeleiteten Felder
|
||||
/// (`StatUmsatz`, `StatKosten`, `Lagerverteilung`, `row_update_time`).
|
||||
/// Stat-Werte werden **linear** aus dem Alt-Stand skaliert (Faktor =
|
||||
/// neue/alte Menge). `VPEMenge` wird NICHT gesetzt — es ist eine
|
||||
/// **berechnete** Spalte und aktualisiert sich automatisch aus `Menge`.
|
||||
/// KEINE Lagerbuchung — das Lager verbucht den Bestand manuell.
|
||||
async fn set_line_menge(
|
||||
client: &mut TiberiusClient,
|
||||
bk_row_id: i64,
|
||||
belegzeilen_nr: i32,
|
||||
menge: i32,
|
||||
lager: &str,
|
||||
) -> Result<(), ApplicationError> {
|
||||
// Alt-Werte lesen (für die lineare Skalierung).
|
||||
let rows = client
|
||||
.query(
|
||||
r#"SELECT CAST(ISNULL(Menge,0) AS FLOAT) AS m,
|
||||
CAST(ISNULL(StatUmsatz,0) AS FLOAT) AS su,
|
||||
CAST(ISNULL(StatKosten,0) AS FLOAT) AS sk
|
||||
FROM Belegzeilen WHERE ParentID = @P1 AND BelegzeilenNr = @P2"#,
|
||||
&[&bk_row_id, &belegzeilen_nr],
|
||||
)
|
||||
.await
|
||||
.map_err(repo)?
|
||||
.into_first_result()
|
||||
.await
|
||||
.map_err(repo)?;
|
||||
let Some(row) = rows.first() else {
|
||||
return Ok(()); // Zeile nicht gefunden → nichts zu tun
|
||||
};
|
||||
let old_m = row.get::<f64, _>("m").unwrap_or(0.0);
|
||||
let su = row.get::<f64, _>("su").unwrap_or(0.0);
|
||||
let sk = row.get::<f64, _>("sk").unwrap_or(0.0);
|
||||
|
||||
let factor = if old_m != 0.0 { menge as f64 / old_m } else { 0.0 };
|
||||
let new_su = su * factor;
|
||||
let new_sk = sk * factor;
|
||||
let lagerverteilung = format!(
|
||||
"<Root>\r\n <Row>\r\n <Lager>{}</Lager>\r\n <Menge>{}</Menge>\r\n </Row>\r\n</Root>",
|
||||
lager.trim(),
|
||||
menge
|
||||
);
|
||||
|
||||
// VPEMenge NICHT setzen (berechnete Spalte → Fehler 271).
|
||||
client
|
||||
.execute(
|
||||
r#"UPDATE Belegzeilen SET Menge = @P1
|
||||
WHERE ParentID = @P2 AND BelegzeilenNr = @P3"#,
|
||||
&[&menge, &bk_row_id, &belegzeilen_nr],
|
||||
r#"UPDATE Belegzeilen
|
||||
SET Menge = @P1, StatUmsatz = @P2,
|
||||
StatKosten = @P3, Lagerverteilung = @P4,
|
||||
row_update_time = GETDATE()
|
||||
WHERE ParentID = @P5 AND BelegzeilenNr = @P6"#,
|
||||
&[
|
||||
&menge,
|
||||
&new_su,
|
||||
&new_sk,
|
||||
&lagerverteilung,
|
||||
&bk_row_id,
|
||||
&belegzeilen_nr,
|
||||
],
|
||||
)
|
||||
.await
|
||||
.map_err(repo)?;
|
||||
@ -250,7 +296,11 @@ impl MssqlErpDeliveryWriteback {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Summiert alle Belegzeilen neu und schreibt die Kopf-Summen.
|
||||
/// Summiert alle Belegzeilen neu und schreibt die Kopf-Summen. TEST:
|
||||
/// zusätzlich `StatKosten`, `kalk` (Kalkulations-XML), `Druckkennzeichen`
|
||||
/// und `row_update_time` — analog zur ERPframe-Engine. `kalk` nutzt die
|
||||
/// (bereits skalierten) Zeilen-Werte; wegen Rundung nicht auf die letzte
|
||||
/// Nachkommastelle deckungsgleich mit der Engine.
|
||||
async fn recalc_head(
|
||||
client: &mut TiberiusClient,
|
||||
bk_row_id: i64,
|
||||
@ -259,7 +309,8 @@ impl MssqlErpDeliveryWriteback {
|
||||
.query(
|
||||
r#"SELECT CAST(ISNULL(Einzelpreis,0) AS FLOAT) AS net,
|
||||
CAST(ISNULL(EinzelPreisBrutto,0) AS FLOAT) AS gross,
|
||||
CAST(ISNULL(Menge,0) AS FLOAT) AS menge
|
||||
CAST(ISNULL(Menge,0) AS FLOAT) AS menge,
|
||||
CAST(ISNULL(StatKosten,0) AS FLOAT) AS statkosten
|
||||
FROM Belegzeilen WHERE ParentID = @P1"#,
|
||||
&[&bk_row_id],
|
||||
)
|
||||
@ -271,44 +322,49 @@ impl MssqlErpDeliveryWriteback {
|
||||
|
||||
let mut net_sum = 0.0_f64;
|
||||
let mut gross_sum = 0.0_f64;
|
||||
let mut statkosten_sum = 0.0_f64;
|
||||
for r in &rows {
|
||||
let net = r.get::<f64, _>("net").unwrap_or(0.0);
|
||||
let gross = r.get::<f64, _>("gross").unwrap_or(0.0);
|
||||
let menge = r.get::<f64, _>("menge").unwrap_or(0.0);
|
||||
let statkosten = r.get::<f64, _>("statkosten").unwrap_or(0.0);
|
||||
net_sum += net * menge;
|
||||
gross_sum += gross * menge;
|
||||
statkosten_sum += statkosten;
|
||||
}
|
||||
|
||||
// Kalkulations-XML (Material = Σ StatKosten, SUMMEVK = Netto-Warenwert).
|
||||
let kalk = format!(
|
||||
"<Root>\r\n <Row>\r\n <Material>{sk}</Material>\r\n \
|
||||
<SUMMEVK>{vk}</SUMMEVK>\r\n <Material_Soll>{sk}</Material_Soll>\r\n \
|
||||
</Row>\r\n</Root>",
|
||||
sk = statkosten_sum,
|
||||
vk = net_sum
|
||||
);
|
||||
let druckkennzeichen: &str = "1";
|
||||
|
||||
client
|
||||
.execute(
|
||||
r#"UPDATE Belegkopf
|
||||
SET WarenwertNetto = @P1, WarenwertBrutto = @P2, PosSummeNetto = @P3
|
||||
WHERE row_id = @P4"#,
|
||||
&[&net_sum, &gross_sum, &net_sum, &bk_row_id],
|
||||
SET WarenwertNetto = @P1, WarenwertBrutto = @P2, PosSummeNetto = @P3,
|
||||
StatKosten = @P4, kalk = @P5, Druckkennzeichen = @P6,
|
||||
row_update_time = GETDATE()
|
||||
WHERE row_id = @P7"#,
|
||||
&[
|
||||
&net_sum,
|
||||
&gross_sum,
|
||||
&net_sum,
|
||||
&statkosten_sum,
|
||||
&kalk,
|
||||
&druckkennzeichen,
|
||||
&bk_row_id,
|
||||
],
|
||||
)
|
||||
.await
|
||||
.map_err(repo)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Setzt Liefer-Zeitpunkt + Status. ISO-8601 mit `T` als String
|
||||
/// (impliziter SQL-Cast), exakt wie das Alt-Makro.
|
||||
async fn mark_delivered(
|
||||
client: &mut TiberiusClient,
|
||||
bk_row_id: i64,
|
||||
delivered_at_iso: &str,
|
||||
) -> Result<(), ApplicationError> {
|
||||
client
|
||||
.execute(
|
||||
r#"UPDATE Belegkopf
|
||||
SET _SV_DELIVERY_DELIVERED_AT = @P1, _SV_DELIVERY_STATE = 'geliefert'
|
||||
WHERE row_id = @P2"#,
|
||||
&[&delivered_at_iso, &bk_row_id],
|
||||
)
|
||||
.await
|
||||
.map_err(repo)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Setzt `Belegkopf.ZahlungsbedingungId` anhand der gewählten Zahlungs-
|
||||
/// methode (Code → ERP-Zahlungsbedingung → deren `row_id`). Idempotent
|
||||
@ -366,12 +422,29 @@ impl MssqlErpDeliveryWriteback {
|
||||
) -> Result<(), ApplicationError> {
|
||||
let bk = Self::resolve_belegkopf(client, cmd.belegart_id, &cmd.belegnummer).await?;
|
||||
|
||||
// Je Belegzeile die Menge auf die ausgelieferte Menge setzen. Umgeht
|
||||
// die ERPframe-Engine (keine Lagerbuchung) — das Lager verbucht den
|
||||
// Bestand manuell nach Rückgabe des Geräts. Geänderte Lieferungen sind
|
||||
// zusätzlich per Vier-Augen-Prüfung markiert.
|
||||
// Default-Lager des Belegs (für die Lagerverteilung der Zeilen).
|
||||
let lager_rows = client
|
||||
.query(
|
||||
"SELECT LTRIM(RTRIM(Lager)) AS lager FROM Belegkopf WHERE row_id = @P1",
|
||||
&[&bk],
|
||||
)
|
||||
.await
|
||||
.map_err(repo)?
|
||||
.into_first_result()
|
||||
.await
|
||||
.map_err(repo)?;
|
||||
let lager = lager_rows
|
||||
.first()
|
||||
.and_then(|r| r.get::<&str, _>("lager"))
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
|
||||
// Je Belegzeile die Menge (+ abgeleitete Felder) auf die ausgelieferte
|
||||
// Menge setzen. Umgeht die ERPframe-Engine (keine Lagerbuchung) — das
|
||||
// Lager verbucht den Bestand manuell nach Rückgabe des Geräts.
|
||||
for line in &cmd.lines {
|
||||
Self::set_line_menge(client, bk, line.belegzeilen_nr, line.delivered_quantity).await?;
|
||||
Self::set_line_menge(client, bk, line.belegzeilen_nr, line.delivered_quantity, &lager)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// Geld-Gutschrift (sofern per Config aktiviert).
|
||||
@ -386,9 +459,6 @@ impl MssqlErpDeliveryWriteback {
|
||||
Self::set_payment_condition(client, bk, code).await?;
|
||||
}
|
||||
|
||||
let iso = cmd.delivered_at.format("%Y-%m-%dT%H:%M:%S").to_string();
|
||||
Self::mark_delivered(client, bk, &iso).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user