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.
|
//! 2. Je Belegzeile die `Menge` auf die ausgelieferte Menge setzen.
|
||||||
//! 3. Gutschrift-Zeile (`GUTSCHRIFT10`) anlegen/aktualisieren.
|
//! 3. Gutschrift-Zeile (`GUTSCHRIFT10`) anlegen/aktualisieren.
|
||||||
//! 4. Belegsummen neu berechnen (`Σ Einzelpreis × Menge`, `Σ Brutto × Menge`).
|
//! 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,
|
//! Hinweis: Das Setzen der Menge per Raw-`UPDATE` umgeht die ERPframe-Engine,
|
||||||
//! erzeugt also KEINE Lagerbuchung. Das ist gewollt — das Lager verbucht den
|
//! 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
|
/// TEST: Setzt die Menge einer Belegzeile auf die ausgelieferte Menge UND
|
||||||
/// tatsächlich ausgelieferte Menge.
|
/// 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(
|
async fn set_line_menge(
|
||||||
client: &mut TiberiusClient,
|
client: &mut TiberiusClient,
|
||||||
bk_row_id: i64,
|
bk_row_id: i64,
|
||||||
belegzeilen_nr: i32,
|
belegzeilen_nr: i32,
|
||||||
menge: i32,
|
menge: i32,
|
||||||
|
lager: &str,
|
||||||
) -> Result<(), ApplicationError> {
|
) -> 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
|
client
|
||||||
.execute(
|
.execute(
|
||||||
r#"UPDATE Belegzeilen SET Menge = @P1
|
r#"UPDATE Belegzeilen
|
||||||
WHERE ParentID = @P2 AND BelegzeilenNr = @P3"#,
|
SET Menge = @P1, StatUmsatz = @P2,
|
||||||
&[&menge, &bk_row_id, &belegzeilen_nr],
|
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
|
.await
|
||||||
.map_err(repo)?;
|
.map_err(repo)?;
|
||||||
@ -250,7 +296,11 @@ impl MssqlErpDeliveryWriteback {
|
|||||||
Ok(())
|
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(
|
async fn recalc_head(
|
||||||
client: &mut TiberiusClient,
|
client: &mut TiberiusClient,
|
||||||
bk_row_id: i64,
|
bk_row_id: i64,
|
||||||
@ -259,7 +309,8 @@ impl MssqlErpDeliveryWriteback {
|
|||||||
.query(
|
.query(
|
||||||
r#"SELECT CAST(ISNULL(Einzelpreis,0) AS FLOAT) AS net,
|
r#"SELECT CAST(ISNULL(Einzelpreis,0) AS FLOAT) AS net,
|
||||||
CAST(ISNULL(EinzelPreisBrutto,0) AS FLOAT) AS gross,
|
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"#,
|
FROM Belegzeilen WHERE ParentID = @P1"#,
|
||||||
&[&bk_row_id],
|
&[&bk_row_id],
|
||||||
)
|
)
|
||||||
@ -271,44 +322,49 @@ impl MssqlErpDeliveryWriteback {
|
|||||||
|
|
||||||
let mut net_sum = 0.0_f64;
|
let mut net_sum = 0.0_f64;
|
||||||
let mut gross_sum = 0.0_f64;
|
let mut gross_sum = 0.0_f64;
|
||||||
|
let mut statkosten_sum = 0.0_f64;
|
||||||
for r in &rows {
|
for r in &rows {
|
||||||
let net = r.get::<f64, _>("net").unwrap_or(0.0);
|
let net = r.get::<f64, _>("net").unwrap_or(0.0);
|
||||||
let gross = r.get::<f64, _>("gross").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 menge = r.get::<f64, _>("menge").unwrap_or(0.0);
|
||||||
|
let statkosten = r.get::<f64, _>("statkosten").unwrap_or(0.0);
|
||||||
net_sum += net * menge;
|
net_sum += net * menge;
|
||||||
gross_sum += gross * 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
|
client
|
||||||
.execute(
|
.execute(
|
||||||
r#"UPDATE Belegkopf
|
r#"UPDATE Belegkopf
|
||||||
SET WarenwertNetto = @P1, WarenwertBrutto = @P2, PosSummeNetto = @P3
|
SET WarenwertNetto = @P1, WarenwertBrutto = @P2, PosSummeNetto = @P3,
|
||||||
WHERE row_id = @P4"#,
|
StatKosten = @P4, kalk = @P5, Druckkennzeichen = @P6,
|
||||||
&[&net_sum, &gross_sum, &net_sum, &bk_row_id],
|
row_update_time = GETDATE()
|
||||||
|
WHERE row_id = @P7"#,
|
||||||
|
&[
|
||||||
|
&net_sum,
|
||||||
|
&gross_sum,
|
||||||
|
&net_sum,
|
||||||
|
&statkosten_sum,
|
||||||
|
&kalk,
|
||||||
|
&druckkennzeichen,
|
||||||
|
&bk_row_id,
|
||||||
|
],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(repo)?;
|
.map_err(repo)?;
|
||||||
Ok(())
|
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-
|
/// Setzt `Belegkopf.ZahlungsbedingungId` anhand der gewählten Zahlungs-
|
||||||
/// methode (Code → ERP-Zahlungsbedingung → deren `row_id`). Idempotent
|
/// methode (Code → ERP-Zahlungsbedingung → deren `row_id`). Idempotent
|
||||||
@ -366,12 +422,29 @@ impl MssqlErpDeliveryWriteback {
|
|||||||
) -> Result<(), ApplicationError> {
|
) -> Result<(), ApplicationError> {
|
||||||
let bk = Self::resolve_belegkopf(client, cmd.belegart_id, &cmd.belegnummer).await?;
|
let bk = Self::resolve_belegkopf(client, cmd.belegart_id, &cmd.belegnummer).await?;
|
||||||
|
|
||||||
// Je Belegzeile die Menge auf die ausgelieferte Menge setzen. Umgeht
|
// Default-Lager des Belegs (für die Lagerverteilung der Zeilen).
|
||||||
// die ERPframe-Engine (keine Lagerbuchung) — das Lager verbucht den
|
let lager_rows = client
|
||||||
// Bestand manuell nach Rückgabe des Geräts. Geänderte Lieferungen sind
|
.query(
|
||||||
// zusätzlich per Vier-Augen-Prüfung markiert.
|
"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 {
|
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).
|
// Geld-Gutschrift (sofern per Config aktiviert).
|
||||||
@ -386,9 +459,6 @@ impl MssqlErpDeliveryWriteback {
|
|||||||
Self::set_payment_condition(client, bk, code).await?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user