u
This commit is contained in:
@@ -14,9 +14,11 @@ OdbcPool& get_pool() { return g_pool; }
|
||||
|
||||
OdbcPool::~OdbcPool() { disconnect(); }
|
||||
|
||||
static void odbc_log_diag(SQLSMALLINT handle_type, SQLHANDLE handle, const char* ctx) {
|
||||
static void odbc_log_diag(SQLSMALLINT handle_type, SQLHANDLE handle, const char* ctx,
|
||||
std::string* capture = nullptr) {
|
||||
if (!handle) {
|
||||
logc::warn("ODBC %s failed (no handle)", ctx);
|
||||
if (capture && capture->empty()) *capture = std::string(ctx) + ": no handle";
|
||||
return;
|
||||
}
|
||||
SQLSMALLINT rec = 0;
|
||||
@@ -30,9 +32,14 @@ static void odbc_log_diag(SQLSMALLINT handle_type, SQLHANDLE handle, const char*
|
||||
if (diag_rc != SQL_SUCCESS && diag_rc != SQL_SUCCESS_WITH_INFO) break;
|
||||
any = true;
|
||||
logc::warn("ODBC %s: %s - %s (%d)", ctx, state, msg, (int)native);
|
||||
if (capture && capture->empty()) {
|
||||
*capture = std::string(reinterpret_cast<char*>(state)) + " - "
|
||||
+ std::string(reinterpret_cast<char*>(msg));
|
||||
}
|
||||
}
|
||||
if (!any) {
|
||||
logc::warn("ODBC %s failed (no diag)", ctx);
|
||||
if (capture && capture->empty()) *capture = std::string(ctx) + ": no diagnostic";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,28 +294,34 @@ static bool fetch_results(SQLHSTMT hstmt, ResultSet& out) {
|
||||
|
||||
bool OdbcPool::execute(Connection* c, const std::string& sql, const std::vector<Param>& params, ResultSet& out) {
|
||||
if (!c) {
|
||||
last_error_ = "no connection";
|
||||
logc::warn("ODBC execute: no connection");
|
||||
return false;
|
||||
}
|
||||
|
||||
SQLRETURN rc = SQLPrepare(c->hstmt, (SQLCHAR*)sql.c_str(), SQL_NTS);
|
||||
if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
|
||||
odbc_log_diag(SQL_HANDLE_STMT, c->hstmt, "prepare");
|
||||
last_error_.clear();
|
||||
odbc_log_diag(SQL_HANDLE_STMT, c->hstmt, "prepare", &last_error_);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<SQLLEN> indicators;
|
||||
if (!params.empty() && !bind_params(c->hstmt, params, indicators)) {
|
||||
odbc_log_diag(SQL_HANDLE_STMT, c->hstmt, "bind");
|
||||
last_error_.clear();
|
||||
odbc_log_diag(SQL_HANDLE_STMT, c->hstmt, "bind", &last_error_);
|
||||
return false;
|
||||
}
|
||||
|
||||
rc = SQLExecute(c->hstmt);
|
||||
if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO && rc != SQL_NO_DATA) {
|
||||
odbc_log_diag(SQL_HANDLE_STMT, c->hstmt, "execute");
|
||||
last_error_.clear();
|
||||
odbc_log_diag(SQL_HANDLE_STMT, c->hstmt, "execute", &last_error_);
|
||||
return false;
|
||||
}
|
||||
|
||||
last_error_.clear();
|
||||
|
||||
fetch_results(c->hstmt, out);
|
||||
|
||||
SQLFreeStmt(c->hstmt, SQL_UNBIND);
|
||||
|
||||
@@ -85,10 +85,13 @@ public:
|
||||
|
||||
void release(Connection* c);
|
||||
|
||||
const std::string& last_error() const { return last_error_; }
|
||||
|
||||
private:
|
||||
SQLHENV henv_ = SQL_NULL_HENV;
|
||||
std::vector<Connection> conns_;
|
||||
std::mutex mutex_;
|
||||
std::string last_error_;
|
||||
Connection* checkout_raw();
|
||||
};
|
||||
|
||||
|
||||
@@ -36,16 +36,30 @@ void handle_order(HttpRequest& req, HttpResponse& resp, RouteContext& /*ctx*/) {
|
||||
|
||||
try {
|
||||
nlohmann::json created = order::create_order(order);
|
||||
logc::success("order %s (kAuftrag=%s) created for externalId=%s",
|
||||
created.value("orderNumber", "").c_str(),
|
||||
created.value("orderId", "").c_str(),
|
||||
externalOrderId.c_str());
|
||||
results.push_back({
|
||||
{"status", "OK"},
|
||||
{"externalOrderId", externalOrderId},
|
||||
{"message", ""}
|
||||
});
|
||||
++successful;
|
||||
if (created.value("alreadyExists", false)) {
|
||||
std::string msg = "order already mapped, skipped save (kAuftrag="
|
||||
+ created.value("orderId", "") + ", "
|
||||
+ created.value("orderNumber", "") + ")";
|
||||
logc::error("order externalId=%s skipped: %s",
|
||||
externalOrderId.c_str(), msg.c_str());
|
||||
results.push_back({
|
||||
{"status", "ERROR"},
|
||||
{"externalOrderId", externalOrderId},
|
||||
{"message", msg}
|
||||
});
|
||||
++failed;
|
||||
} else {
|
||||
logc::success("order %s (kAuftrag=%s) created for externalId=%s",
|
||||
created.value("orderNumber", "").c_str(),
|
||||
created.value("orderId", "").c_str(),
|
||||
externalOrderId.c_str());
|
||||
results.push_back({
|
||||
{"status", "OK"},
|
||||
{"externalOrderId", externalOrderId},
|
||||
{"message", ""}
|
||||
});
|
||||
++successful;
|
||||
}
|
||||
} catch (const std::exception& ex) {
|
||||
logc::error("order externalId=%s failed: %s", externalOrderId.c_str(), ex.what());
|
||||
results.push_back({
|
||||
@@ -57,7 +71,8 @@ void handle_order(HttpRequest& req, HttpResponse& resp, RouteContext& /*ctx*/) {
|
||||
}
|
||||
}
|
||||
|
||||
resp.send_json(200, results);
|
||||
int http_status = (failed > 0) ? 500 : 200;
|
||||
resp.send_json(http_status, results);
|
||||
|
||||
if (orders.empty()) {
|
||||
logc::info("POST /v1/order: no orders in body");
|
||||
|
||||
@@ -25,7 +25,10 @@ inline void commit_picklists(OdbcPool::Connection* c, int kBenutzer, int kSessio
|
||||
};
|
||||
ResultSet rs;
|
||||
if (!get_pool().execute(c, sql, ps, rs)) {
|
||||
throw std::runtime_error("commit_picklists failed");
|
||||
const std::string& detail = get_pool().last_error();
|
||||
throw std::runtime_error(detail.empty()
|
||||
? "commit_picklists failed"
|
||||
: "commit_picklists failed: " + detail);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
#include <ctime>
|
||||
#include <chrono>
|
||||
@@ -297,6 +298,26 @@ inline bool is_walk_in_order(const nlohmann::json& order) {
|
||||
&& billing.value("firstName", "").empty();
|
||||
}
|
||||
|
||||
inline std::string resolve_auftrag_c_kunden_nr(const nlohmann::json& order) {
|
||||
if (is_walk_in_order(order)) return "0";
|
||||
return order.value("customerNumber", "");
|
||||
}
|
||||
|
||||
// POS settings.invoiceSetting: 0 = external/POS invoice (nIstExterneRechnung=1).
|
||||
inline int resolve_n_ist_externe_rechnung(const nlohmann::json& order) {
|
||||
if (!order.contains("settings") || order["settings"].is_null()) return 1;
|
||||
const auto& settings = order["settings"];
|
||||
if (!settings.contains("invoiceSetting")) return 1;
|
||||
const auto& inv = settings["invoiceSetting"];
|
||||
if (inv.is_boolean()) return inv.get<bool>() ? 0 : 1;
|
||||
if (inv.is_number_integer()) return inv.get<int>() == 1 ? 0 : 1;
|
||||
if (inv.is_string()) {
|
||||
std::string s = inv.get<std::string>();
|
||||
return (s == "1" || s == "true") ? 0 : 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
inline std::pair<int,int> create_customer(OdbcPool::Connection* c, const std::string& customer_number,
|
||||
const nlohmann::json& address, const Defaults& defaults) {
|
||||
const nlohmann::json a = address.is_null() ? nlohmann::json::object() : address;
|
||||
@@ -484,14 +505,27 @@ inline int parse_pos_auftrag_id(const std::string& external_id) {
|
||||
try { return std::stoi(external_id); } catch (...) { return 0; }
|
||||
}
|
||||
|
||||
inline bool external_order_numbers_match(const std::string& mapped, const std::string& incoming) {
|
||||
if (incoming.empty()) return true;
|
||||
if (mapped.size() != incoming.size()) return false;
|
||||
for (size_t i = 0; i < mapped.size(); ++i) {
|
||||
if (std::tolower(static_cast<unsigned char>(mapped[i]))
|
||||
!= std::tolower(static_cast<unsigned char>(incoming[i]))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// PosOrderCreationService.CheckIfOrderExists — already-imported POS order.
|
||||
inline std::optional<std::pair<int, std::string>> find_existing_pos_order(
|
||||
OdbcPool::Connection* c, int kPosAuftrag) {
|
||||
OdbcPool::Connection* c, int kPosAuftrag, const std::string& external_order_number) {
|
||||
int kShopSubShop = get_active_shop_subshop_id();
|
||||
if (kPosAuftrag <= 0 || kShopSubShop <= 0) return std::nullopt;
|
||||
|
||||
const char* sql =
|
||||
"SELECT TOP 1 m.kAuftrag, a.cAuftragsNr FROM Pos.tAuftragMapping m "
|
||||
"SELECT TOP 1 m.kAuftrag, a.cAuftragsNr, ISNULL(a.cExterneAuftragsnummer, '') AS cExterneAuftragsnummer "
|
||||
"FROM Pos.tAuftragMapping m "
|
||||
"LEFT JOIN Verkauf.tAuftrag a ON a.kAuftrag = m.kAuftrag "
|
||||
"WHERE m.kPosAuftrag = ? AND m.kShopSubShop = ? AND m.kAuftrag IS NOT NULL "
|
||||
"ORDER BY m.kAuftrag DESC";
|
||||
@@ -509,6 +543,10 @@ inline std::optional<std::pair<int, std::string>> find_existing_pos_order(
|
||||
int kAuftrag = std::stoi(rs[0][0].str);
|
||||
if (kAuftrag <= 0) return std::nullopt;
|
||||
std::string order_number = rs[0].size() > 1 ? rs[0][1].str : "";
|
||||
std::string mapped_external = rs[0].size() > 2 ? rs[0][2].str : "";
|
||||
if (!external_order_numbers_match(mapped_external, external_order_number)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return std::make_pair(kAuftrag, order_number);
|
||||
}
|
||||
|
||||
@@ -516,22 +554,23 @@ inline void upsert_pos_order_mapping(OdbcPool::Connection* c, int kAuftrag, int
|
||||
int kShopSubShop = get_active_shop_subshop_id();
|
||||
if (kPosAuftrag <= 0 || kShopSubShop <= 0) return;
|
||||
|
||||
const char* sql =
|
||||
"MERGE INTO Pos.tAuftragMapping WITH (HOLDLOCK) AS Target "
|
||||
"USING (SELECT ? AS kAuftrag, ? AS kPosAuftrag, ? AS kShopSubShop) AS Source "
|
||||
"ON Target.kPosAuftrag = Source.kPosAuftrag AND Target.kShopSubShop = Source.kShopSubShop "
|
||||
"WHEN MATCHED THEN UPDATE SET Target.kAuftrag = Source.kAuftrag "
|
||||
"WHEN NOT MATCHED BY TARGET THEN "
|
||||
"INSERT (kAuftrag, kPosAuftrag, kShopSubShop) "
|
||||
"VALUES (Source.kAuftrag, Source.kPosAuftrag, Source.kShopSubShop)";
|
||||
ResultSet rs;
|
||||
std::vector<Param> del_ps = {
|
||||
{ParamType::Int, "", kPosAuftrag},
|
||||
{ParamType::Int, "", kShopSubShop},
|
||||
};
|
||||
get_pool().execute(c,
|
||||
"DELETE FROM Pos.tAuftragMapping WHERE kPosAuftrag = ? AND kShopSubShop = ?",
|
||||
del_ps, rs);
|
||||
|
||||
std::vector<Param> ps = {
|
||||
std::vector<Param> ins_ps = {
|
||||
{ParamType::Int, "", kAuftrag},
|
||||
{ParamType::Int, "", kPosAuftrag},
|
||||
{ParamType::Int, "", kShopSubShop}
|
||||
{ParamType::Int, "", kShopSubShop},
|
||||
};
|
||||
ResultSet rs;
|
||||
get_pool().execute(c, sql, ps, rs);
|
||||
get_pool().execute(c,
|
||||
"INSERT INTO Pos.tAuftragMapping (kAuftrag, kPosAuftrag, kShopSubShop) VALUES (?, ?, ?)",
|
||||
ins_ps, rs);
|
||||
}
|
||||
|
||||
inline void insert_pos_order_position_mapping(OdbcPool::Connection* c, int kAuftragPosition,
|
||||
@@ -606,11 +645,13 @@ inline nlohmann::json create_order(const nlohmann::json& order) {
|
||||
auto* c = guard.get();
|
||||
|
||||
int kPosAuftrag = parse_pos_auftrag_id(order.value("externalId", ""));
|
||||
std::string external_order_number = order.value("externalOrderNumber", "");
|
||||
if (kPosAuftrag > 0) {
|
||||
if (auto existing = find_existing_pos_order(c, kPosAuftrag)) {
|
||||
if (auto existing = find_existing_pos_order(c, kPosAuftrag, external_order_number)) {
|
||||
return {
|
||||
{"orderId", std::to_string(existing->first)},
|
||||
{"orderNumber", existing->second}
|
||||
{"orderNumber", existing->second},
|
||||
{"alreadyExists", true}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -645,12 +686,12 @@ inline nlohmann::json create_order(const nlohmann::json& order) {
|
||||
" (cAuftragsNr, dErstellt, nKomplettAusgeliefert, kBenutzer, kKunde, kBenutzerErstellt, nType, fFaktor,"
|
||||
" kFirmaHistory, kSprache, cVersandlandWaehrung, fVersandlandWaehrungFaktor, fFinanzierungskosten,"
|
||||
" cWaehrung, kPlattform, kShop, cKundenNr, cVersandlandISO, kVersandArt, kZahlungsart, kKundengruppe,"
|
||||
" cExterneAuftragsnummer)"
|
||||
" cExterneAuftragsnummer, nIstExterneRechnung, cInet, nIstReadOnly, kShopauftrag, nLieferPrioritaet)"
|
||||
" OUTPUT inserted.kAuftrag INTO @t"
|
||||
" VALUES (?, ?, 0, ?, ?, ?, 1, 1.0,"
|
||||
" ?, ?, ?, 1.0, 0.0,"
|
||||
" ?, ?, ?, ?, ?, ?, ?, ?,"
|
||||
" ?);"
|
||||
" ?, ?, ?, ?, 0, 10);"
|
||||
"SELECT kAuftrag FROM @t";
|
||||
|
||||
std::string shipping_iso = order.value("shippingAddress", nlohmann::json::object()).value("countryIso", "DE");
|
||||
@@ -658,6 +699,8 @@ inline nlohmann::json create_order(const nlohmann::json& order) {
|
||||
[](unsigned char ch) { return std::toupper(ch); });
|
||||
|
||||
std::string currency_iso = order.value("currencyIso", "EUR");
|
||||
std::string cKundenNr = resolve_auftrag_c_kunden_nr(order);
|
||||
int nIstExterneRechnung = resolve_n_ist_externe_rechnung(order);
|
||||
std::vector<Param> ps = {
|
||||
{ParamType::NVarChar, cAuftragsNr, 0},
|
||||
{ParamType::NVarChar, order_date_sql(order_date_tm), 0},
|
||||
@@ -670,12 +713,15 @@ inline nlohmann::json create_order(const nlohmann::json& order) {
|
||||
{ParamType::NVarChar, currency_iso, 0},
|
||||
{ParamType::Int, "", defaults.kPlattform},
|
||||
{ParamType::Int, "", active_shop == 0 ? -1 : active_shop},
|
||||
{ParamType::NVarChar, order.value("customerNumber", ""), 0},
|
||||
{ParamType::NVarChar, cKundenNr, 0},
|
||||
{ParamType::NVarChar, shipping_iso, 0},
|
||||
{ParamType::Int, "", defaults.kVersandArt},
|
||||
{ParamType::Int, "", json_int(zahlungsart, "kZahlungsart", 0)},
|
||||
{ParamType::Int, "", kKundengruppe},
|
||||
{ParamType::NVarChar, order.value("externalOrderNumber", ""), 0}
|
||||
{ParamType::NVarChar, order.value("externalOrderNumber", ""), 0},
|
||||
{ParamType::Int, "", nIstExterneRechnung},
|
||||
{ParamType::NVarChar, "Y", 0},
|
||||
{ParamType::Int, "", 2},
|
||||
};
|
||||
if (active_shop == 0) {
|
||||
ps[11] = Param::null_int();
|
||||
@@ -719,7 +765,11 @@ inline nlohmann::json create_order(const nlohmann::json& order) {
|
||||
|
||||
if (!get_pool().commit(c)) throw std::runtime_error("failed to commit transaction");
|
||||
|
||||
return { {"orderId", std::to_string(kAuftrag)}, {"orderNumber", cAuftragsNr} };
|
||||
return {
|
||||
{"orderId", std::to_string(kAuftrag)},
|
||||
{"orderNumber", cAuftragsNr},
|
||||
{"alreadyExists", false}
|
||||
};
|
||||
} catch (const std::exception&) {
|
||||
try { get_pool().rollback(c); } catch (...) {}
|
||||
throw;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "session.hpp"
|
||||
#include "warehouse.hpp"
|
||||
#include "reserve.hpp"
|
||||
#include "stock_shortage.hpp"
|
||||
#include "commit.hpp"
|
||||
#include "deliver.hpp"
|
||||
#include "../db/pool.hpp"
|
||||
@@ -14,10 +15,13 @@ inline void deliver_order(OdbcPool::Connection* c, int kBenutzer, int kAuftrag,
|
||||
if (items.empty()) return;
|
||||
|
||||
int kWarenLager = resolve_outgoing_warehouse(c);
|
||||
int kWarenLagerPlatz = resolve_warehouse_place(c, kWarenLager);
|
||||
int kSessionId = open_session(c, kBenutzer);
|
||||
|
||||
try {
|
||||
reserve_positions(c, kBenutzer, kSessionId, kWarenLager, items);
|
||||
book_stock_shortfalls_and_rereserve(c, kBenutzer, kSessionId, kWarenLager,
|
||||
kWarenLagerPlatz, items);
|
||||
commit_picklists(c, kBenutzer, kSessionId, kAuftrag);
|
||||
deliver_picklists(c, kBenutzer, kSessionId, kAuftrag, kVersandArt);
|
||||
} catch (...) {
|
||||
|
||||
@@ -50,7 +50,10 @@ inline void reserve_positions(OdbcPool::Connection* c, int kBenutzer, int kSessi
|
||||
};
|
||||
ResultSet rs;
|
||||
if (!get_pool().execute(c, sql, ps, rs)) {
|
||||
throw std::runtime_error("reserve_positions failed");
|
||||
const std::string& detail = get_pool().last_error();
|
||||
throw std::runtime_error(detail.empty()
|
||||
? "reserve_positions failed"
|
||||
: "reserve_positions failed: " + detail);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
137
jtlsrv-cpp/src/queries/stock_shortage.hpp
Normal file
137
jtlsrv-cpp/src/queries/stock_shortage.hpp
Normal file
@@ -0,0 +1,137 @@
|
||||
#pragma once
|
||||
#include <cmath>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "reserve.hpp"
|
||||
#include "warehouse.hpp"
|
||||
#include "../db/pool.hpp"
|
||||
#include "../log.hpp"
|
||||
|
||||
namespace delivery {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr const char* POS_SHORTAGE_COMMENT = "Korrekturbuchung erstellt durch POS-Abgleich";
|
||||
constexpr int BUCHUNGSART_WARENEINGANG = 10;
|
||||
|
||||
inline double get_reserved_quantity(OdbcPool::Connection* c, int kSessionId, int kAuftragPosition) {
|
||||
const char* sql =
|
||||
"SELECT ISNULL(SUM(pp.fAnzahl), 0) FROM dbo.tPicklistePos pp "
|
||||
"INNER JOIN dbo.tPickliste p ON p.kPickliste = pp.kPickliste "
|
||||
"WHERE p.kSessionId = ? AND pp.kBestellPos = ?";
|
||||
|
||||
std::vector<Param> ps = {
|
||||
{ParamType::Int, "", kSessionId},
|
||||
{ParamType::Int, "", kAuftragPosition},
|
||||
};
|
||||
ResultSet rs;
|
||||
if (!get_pool().execute(c, sql, ps, rs) || rs.empty() || rs[0].empty()) {
|
||||
return 0.0;
|
||||
}
|
||||
try {
|
||||
return std::stod(rs[0][0].str);
|
||||
} catch (...) {
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
inline int get_position_artikel(OdbcPool::Connection* c, int kAuftragPosition) {
|
||||
std::vector<Param> ps = {{ParamType::Int, "", kAuftragPosition}};
|
||||
ResultSet rs;
|
||||
if (!get_pool().execute(c,
|
||||
"SELECT kArtikel FROM Verkauf.tAuftragPosition WHERE kAuftragPosition = ?",
|
||||
ps, rs) || rs.empty() || rs[0].empty() || rs[0][0].type == CellType::Null) {
|
||||
return 0;
|
||||
}
|
||||
try {
|
||||
return std::stoi(rs[0][0].str);
|
||||
} catch (...) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
inline void book_wareneingang(OdbcPool::Connection* c, int kBenutzer,
|
||||
int kWarenLagerPlatz, int kArtikel, double fehlmenge) {
|
||||
const char* sql =
|
||||
"DECLARE @kWarenlagerEingang INT;"
|
||||
"EXEC dbo.spWarenlagerEingangSchreiben"
|
||||
" @kArtikel = ?,"
|
||||
" @kWarenLagerPlatz = ?,"
|
||||
" @kLieferantenBestellungPos = 0,"
|
||||
" @kBenutzer = ?,"
|
||||
" @fAnzahl = ?,"
|
||||
" @fEkEinzel = 0,"
|
||||
" @cLieferscheinNr = '',"
|
||||
" @cChargenNr = NULL,"
|
||||
" @dMHD = NULL,"
|
||||
" @dGeliefertAm = NULL,"
|
||||
" @cKommentar = ?,"
|
||||
" @kGutschriftPos = 0,"
|
||||
" @kLHM = 0,"
|
||||
" @kSessionId = 0,"
|
||||
" @kBuchungsart = ?,"
|
||||
" @kBestellPosUmlagerung = 0,"
|
||||
" @kRMRetourePos = 0,"
|
||||
" @nHistorieNichtSchreiben = 0,"
|
||||
" @kWarenlagerEingang = @kWarenlagerEingang OUTPUT;"
|
||||
"SELECT @kWarenlagerEingang AS kWarenlagerEingang;";
|
||||
|
||||
std::vector<Param> ps = {
|
||||
{ParamType::Int, "", kArtikel},
|
||||
{ParamType::Int, "", kWarenLagerPlatz},
|
||||
{ParamType::Int, "", kBenutzer},
|
||||
{ParamType::Double, "", 0, fehlmenge},
|
||||
{ParamType::NVarChar, POS_SHORTAGE_COMMENT, 0},
|
||||
{ParamType::Int, "", BUCHUNGSART_WARENEINGANG},
|
||||
};
|
||||
ResultSet rs;
|
||||
if (!get_pool().execute(c, sql, ps, rs)) {
|
||||
const std::string& detail = get_pool().last_error();
|
||||
throw std::runtime_error(detail.empty()
|
||||
? "spWarenlagerEingangSchreiben failed"
|
||||
: "spWarenlagerEingangSchreiben failed: " + detail);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// PosStockPositionService.FehlbestandEinbuchen — book missing stock, then re-reserve.
|
||||
inline void book_stock_shortfalls_and_rereserve(OdbcPool::Connection* c, int kBenutzer,
|
||||
int kSessionId, int kWarenLager,
|
||||
int kWarenLagerPlatz,
|
||||
const std::vector<DeliveredItem>& items) {
|
||||
std::vector<DeliveredItem> rereserve;
|
||||
|
||||
for (const auto& item : items) {
|
||||
if (item.kAuftragPosition <= 0 || item.quantity <= 0.0) continue;
|
||||
|
||||
double reserved = get_reserved_quantity(c, kSessionId, item.kAuftragPosition);
|
||||
double shortage = item.quantity - reserved;
|
||||
if (shortage <= 0.0001) continue;
|
||||
|
||||
int kArtikel = get_position_artikel(c, item.kAuftragPosition);
|
||||
if (kArtikel <= 0) continue;
|
||||
|
||||
logc::info("POS shortage booking: kBestellPos=%d kArtikel=%d reserved=%.4f need=%.4f book=%.4f",
|
||||
item.kAuftragPosition, kArtikel, reserved, item.quantity, shortage);
|
||||
book_wareneingang(c, kBenutzer, kWarenLagerPlatz, kArtikel, shortage);
|
||||
rereserve.push_back({item.kAuftragPosition, shortage});
|
||||
}
|
||||
|
||||
if (!rereserve.empty()) {
|
||||
reserve_positions(c, kBenutzer, kSessionId, kWarenLager, rereserve);
|
||||
}
|
||||
|
||||
for (const auto& item : items) {
|
||||
if (item.kAuftragPosition <= 0 || item.quantity <= 0.0) continue;
|
||||
double reserved = get_reserved_quantity(c, kSessionId, item.kAuftragPosition);
|
||||
if (reserved + 0.0001 < item.quantity) {
|
||||
throw std::runtime_error(
|
||||
"insufficient stock after POS shortage booking for kBestellPos="
|
||||
+ std::to_string(item.kAuftragPosition));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace delivery
|
||||
@@ -18,4 +18,20 @@ inline int resolve_outgoing_warehouse(OdbcPool::Connection* c) {
|
||||
return std::stoi(rs[0][0].str);
|
||||
}
|
||||
|
||||
inline int resolve_warehouse_place(OdbcPool::Connection* c, int kWarenLager) {
|
||||
int configured = config::get_int("JTL_KWARENLAGERPLATZ", 0);
|
||||
if (configured > 0) return configured;
|
||||
|
||||
std::vector<Param> ps = {{ParamType::Int, "", kWarenLager}};
|
||||
ResultSet rs;
|
||||
if (!get_pool().execute(c,
|
||||
"SELECT TOP 1 kWarenLagerPlatz FROM dbo.tWarenLagerPlatz "
|
||||
"WHERE kWarenLager = ? AND ISNULL(nGesperrt, 0) = 0 "
|
||||
"ORDER BY nPrio, kWarenLagerPlatz", ps, rs) || rs.empty() || rs[0].empty()) {
|
||||
throw std::runtime_error("No warehouse place found for kWarenLager="
|
||||
+ std::to_string(kWarenLager) + "; set JTL_KWARENLAGERPLATZ explicitly.");
|
||||
}
|
||||
return std::stoi(rs[0][0].str);
|
||||
}
|
||||
|
||||
} // namespace delivery
|
||||
|
||||
Reference in New Issue
Block a user