967 lines
39 KiB
C++
967 lines
39 KiB
C++
#pragma once
|
|
#include <algorithm>
|
|
#include <cctype>
|
|
#include <cmath>
|
|
#include <ctime>
|
|
#include <chrono>
|
|
#include <iomanip>
|
|
#include <map>
|
|
#include <optional>
|
|
#include <memory>
|
|
#include <sstream>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <vector>
|
|
#include "delivery.hpp"
|
|
#include "shop.hpp"
|
|
#include "../db/pool.hpp"
|
|
#include "../config.hpp"
|
|
#include "nlohmann/json.hpp"
|
|
|
|
namespace order {
|
|
|
|
struct Defaults;
|
|
|
|
namespace {
|
|
|
|
struct Config {
|
|
int kBenutzer = 1;
|
|
int kFirmaHistory = 0;
|
|
int kSprache = 1;
|
|
int kPlattform = 7;
|
|
int kVersandArt = 0;
|
|
int kKundengruppe = 0;
|
|
int orderNumberSequence = 3;
|
|
int customerNumberSequence = 6;
|
|
};
|
|
|
|
constexpr int VERSANDPOSITION_TYPE = 2;
|
|
constexpr int ZAHLUNG_TYPE_ZAHLUNG = 10;
|
|
constexpr int NIST_READONLY_NICHT_AENDERBAR = 2;
|
|
constexpr int NIST_EXTERNE_RECHNUNG_KEINE = 2;
|
|
|
|
Config g_config;
|
|
std::unique_ptr<Defaults> g_resolved_defaults;
|
|
std::map<std::string, nlohmann::json> g_zahlungsart_cache;
|
|
|
|
} // namespace
|
|
|
|
struct Defaults {
|
|
int kFirmaHistory = 1;
|
|
int kVersandArt = 1;
|
|
int kKundengruppe = 1;
|
|
int kPlattform = 1;
|
|
};
|
|
|
|
inline void load_config() {
|
|
g_config.kBenutzer = config::get_int("JTL_KBENUTZER", 1);
|
|
g_config.kFirmaHistory = config::get_int("JTL_KFIRMAHISTORY", 0);
|
|
g_config.kSprache = config::get_int("JTL_KSPRACHE", 1);
|
|
g_config.kPlattform = config::get_int("JTL_KPLATTFORM", 7);
|
|
g_config.kVersandArt = config::get_int("JTL_KVERSANDART", 0);
|
|
g_config.kKundengruppe = config::get_int("JTL_KKUNDENGRUPPE", 0);
|
|
g_config.orderNumberSequence = config::get_int("JTL_ORDER_NUMBER_SEQUENCE", 3);
|
|
g_config.customerNumberSequence = config::get_int("JTL_CUSTOMER_NUMBER_SEQUENCE", 6);
|
|
}
|
|
|
|
inline const Defaults& get_defaults(OdbcPool::Connection* c) {
|
|
if (g_resolved_defaults) return *g_resolved_defaults;
|
|
|
|
Defaults d;
|
|
d.kFirmaHistory = g_config.kFirmaHistory;
|
|
d.kVersandArt = g_config.kVersandArt;
|
|
d.kKundengruppe = g_config.kKundengruppe;
|
|
d.kPlattform = g_config.kPlattform;
|
|
|
|
const char* sql =
|
|
"SELECT"
|
|
" (SELECT MAX(kFirmaHistory) FROM dbo.tFirmaHistory) AS kFirmaHistory,"
|
|
" (SELECT MIN(kVersandArt) FROM dbo.tVersandArt) AS kVersandArt,"
|
|
" (SELECT TOP 1 kKundenGruppe FROM dbo.tKundenGruppe ORDER BY nStandard DESC, kKundenGruppe) AS kKundengruppe,"
|
|
" (SELECT CASE WHEN EXISTS (SELECT 1 FROM dbo.tPlattform WHERE nPlattform = ?) THEN ? ELSE 1 END) AS kPlattform";
|
|
|
|
std::vector<Param> ps = {
|
|
{ParamType::Int, "", g_config.kPlattform},
|
|
{ParamType::Int, "", g_config.kPlattform}
|
|
};
|
|
ResultSet rs;
|
|
if (get_pool().execute(c, sql, ps, rs) && !rs.empty() && !rs[0].empty()) {
|
|
if (!rs[0][0].str.empty()) d.kFirmaHistory = std::stoi(rs[0][0].str);
|
|
if (!rs[0][1].str.empty()) d.kVersandArt = std::stoi(rs[0][1].str);
|
|
if (!rs[0][2].str.empty()) d.kKundengruppe = std::stoi(rs[0][2].str);
|
|
if (!rs[0][3].str.empty()) d.kPlattform = std::stoi(rs[0][3].str);
|
|
}
|
|
|
|
// Apply environment overrides
|
|
if (g_config.kFirmaHistory > 0) d.kFirmaHistory = g_config.kFirmaHistory;
|
|
if (g_config.kVersandArt > 0) d.kVersandArt = g_config.kVersandArt;
|
|
if (g_config.kKundengruppe > 0) d.kKundengruppe = g_config.kKundengruppe;
|
|
|
|
g_resolved_defaults = std::make_unique<Defaults>(d);
|
|
return *g_resolved_defaults;
|
|
}
|
|
|
|
inline double to_number(const nlohmann::json& value, double fallback = 0) {
|
|
if (value.is_null()) return fallback;
|
|
if (value.is_number()) return value.get<double>();
|
|
if (value.is_string()) {
|
|
std::string s = value.get<std::string>();
|
|
if (s.empty()) return fallback;
|
|
std::replace(s.begin(), s.end(), ',', '.');
|
|
try { return std::stod(s); } catch (...) {}
|
|
}
|
|
return fallback;
|
|
}
|
|
|
|
inline int to_int(const nlohmann::json& value, int fallback = 0) {
|
|
if (value.is_null()) return fallback;
|
|
if (value.is_number_integer()) return value.get<int>();
|
|
if (value.is_number_unsigned()) return static_cast<int>(value.get<unsigned>());
|
|
if (value.is_number_float()) return static_cast<int>(value.get<double>());
|
|
if (value.is_boolean()) return value.get<bool>() ? 1 : 0;
|
|
if (value.is_string()) {
|
|
std::string s = value.get<std::string>();
|
|
if (s.empty()) return fallback;
|
|
try { return std::stoi(s); } catch (...) {}
|
|
}
|
|
return fallback;
|
|
}
|
|
|
|
inline int json_int(const nlohmann::json& obj, const char* key, int fallback = 0) {
|
|
auto it = obj.find(key);
|
|
if (it == obj.end()) return fallback;
|
|
return to_int(*it, fallback);
|
|
}
|
|
|
|
inline double json_double(const nlohmann::json& obj, const char* key, double fallback = 0) {
|
|
auto it = obj.find(key);
|
|
if (it == obj.end()) return fallback;
|
|
return to_number(*it, fallback);
|
|
}
|
|
|
|
inline int steuerklasse_for_vat(double vat) {
|
|
if (vat >= 15) return 1;
|
|
if (vat > 0) return 2;
|
|
return 1;
|
|
}
|
|
|
|
inline int iso_week(const std::tm& tm) {
|
|
// JTL placeholders: J = Jahr, M = Monat, T = Tag, K = Kalenderwoche.
|
|
// ISO week date
|
|
std::tm t0 = tm;
|
|
int yday = t0.tm_yday;
|
|
int wday = t0.tm_wday;
|
|
if (wday == 0) wday = 7;
|
|
int week = (yday - wday + 10) / 7;
|
|
if (week < 1) week = 1;
|
|
if (week > 53) week = 53;
|
|
return week;
|
|
}
|
|
|
|
inline std::string format_number_placeholders(const std::string& tpl, const std::tm& tm) {
|
|
if (tpl.empty()) return "";
|
|
std::string r = tpl;
|
|
auto replace = [&r](const std::string& from, const std::string& to) {
|
|
size_t start = 0;
|
|
while ((start = r.find(from, start)) != std::string::npos) {
|
|
r.replace(start, from.size(), to);
|
|
start += to.size();
|
|
}
|
|
};
|
|
std::ostringstream mm, dd, kk;
|
|
mm << std::setw(2) << std::setfill('0') << (tm.tm_mon + 1);
|
|
dd << std::setw(2) << std::setfill('0') << tm.tm_mday;
|
|
kk << std::setw(2) << std::setfill('0') << iso_week(tm);
|
|
replace("<J>", std::to_string(tm.tm_year + 1900));
|
|
replace("<M>", mm.str());
|
|
replace("<T>", dd.str());
|
|
replace("<K>", kk.str());
|
|
return r;
|
|
}
|
|
|
|
inline std::tm parse_order_date(const std::string& s) {
|
|
std::tm tm{};
|
|
std::string iso = s;
|
|
std::replace(iso.begin(), iso.end(), ' ', 'T');
|
|
std::istringstream ss(iso);
|
|
ss >> std::get_time(&tm, "%Y-%m-%dT%H:%M:%S");
|
|
if (ss.fail()) {
|
|
auto now = std::chrono::system_clock::now();
|
|
std::time_t t = std::chrono::system_clock::to_time_t(now);
|
|
localtime_r(&t, &tm);
|
|
}
|
|
return tm;
|
|
}
|
|
|
|
inline std::string order_date_sql(const std::tm& tm) {
|
|
char buf[32];
|
|
std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &tm);
|
|
return buf;
|
|
}
|
|
|
|
inline std::string country_name(const std::string& iso) {
|
|
static const std::map<std::string, std::string> names = {
|
|
{"DE", "Deutschland"},
|
|
{"AT", "Oesterreich"},
|
|
{"CH", "Schweiz"}
|
|
};
|
|
auto it = names.find(iso);
|
|
return it != names.end() ? it->second : iso;
|
|
}
|
|
|
|
inline std::string next_number_from_sequence(OdbcPool::Connection* c, int kLaufendeNummer,
|
|
const std::tm& date) {
|
|
const char* sql =
|
|
"DECLARE @n INT, @cPrefix NVARCHAR(50), @cSuffix NVARCHAR(50);"
|
|
"UPDATE dbo.tLaufendeNummern"
|
|
" SET @n = nNummer = nNummer + 1, @cPrefix = cPrefix, @cSuffix = cSuffix"
|
|
" WHERE kLaufendeNummer = ?;"
|
|
"SELECT @n AS nNummer, @cPrefix AS cPrefix, @cSuffix AS cSuffix";
|
|
|
|
std::vector<Param> ps = {{ParamType::Int, "", kLaufendeNummer}};
|
|
ResultSet rs;
|
|
if (!get_pool().execute(c, sql, ps, rs) || rs.empty() || rs[0].size() < 3) {
|
|
throw std::runtime_error("dbo.tLaufendeNummern has no row " + std::to_string(kLaufendeNummer));
|
|
}
|
|
int n = std::stoi(rs[0][0].str);
|
|
std::string prefix = format_number_placeholders(rs[0][1].str, date);
|
|
std::string suffix = format_number_placeholders(rs[0][2].str, date);
|
|
return prefix + std::to_string(n) + suffix;
|
|
}
|
|
|
|
inline int allocate_pk(OdbcPool::Connection* c, const std::string& tableName) {
|
|
const char* sql =
|
|
"DECLARE @pk INT;"
|
|
"UPDATE dbo.tpk SET @pk = nummer, nummer = nummer + 1, dChanged = GETDATE() WHERE cName = ?;"
|
|
"SELECT @pk AS pk";
|
|
|
|
std::vector<Param> ps = {{ParamType::NVarChar, tableName, 0}};
|
|
ResultSet rs;
|
|
if (!get_pool().execute(c, sql, ps, rs) || rs.empty() || rs[0].empty()) {
|
|
throw std::runtime_error("dbo.tpk has no row for table '" + tableName + "'");
|
|
}
|
|
return std::stoi(rs[0][0].str);
|
|
}
|
|
|
|
inline int parse_import_setting(const nlohmann::json& order) {
|
|
if (!order.contains("settings") || order["settings"].is_null()) return 0;
|
|
return json_int(order["settings"], "importSetting", 0);
|
|
}
|
|
|
|
inline int parse_invoice_setting(const nlohmann::json& order) {
|
|
if (!order.contains("settings") || order["settings"].is_null()) return 0;
|
|
return json_int(order["settings"], "invoiceSetting", 0);
|
|
}
|
|
|
|
inline int resolve_n_ist_readonly(const nlohmann::json& order) {
|
|
return parse_import_setting(order) == 0 ? NIST_READONLY_NICHT_AENDERBAR : 0;
|
|
}
|
|
|
|
inline int resolve_n_ist_externe_rechnung(const nlohmann::json& order) {
|
|
const int import_setting = parse_import_setting(order);
|
|
const int invoice_setting = parse_invoice_setting(order);
|
|
if (import_setting >= 2 && import_setting <= 5) return 0;
|
|
if (invoice_setting & 1) return 0;
|
|
if (import_setting == 0) return NIST_EXTERNE_RECHNUNG_KEINE;
|
|
return 0;
|
|
}
|
|
|
|
struct VersandArtRow {
|
|
int kVersandArt = 0;
|
|
std::string cName;
|
|
double fPrice = 0;
|
|
double fMwSt = 19;
|
|
};
|
|
|
|
inline bool is_versandposition(const nlohmann::json& item) {
|
|
return json_int(item, "type", 0) == VERSANDPOSITION_TYPE;
|
|
}
|
|
|
|
inline bool has_versandposition(const nlohmann::json& items) {
|
|
if (!items.is_array()) return false;
|
|
for (const auto& item : items) {
|
|
if (is_versandposition(item)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
inline bool has_non_return_sale_items(const nlohmann::json& items) {
|
|
if (!items.is_array()) return false;
|
|
for (const auto& item : items) {
|
|
if (json_double(item, "isReturn", 0) == 0) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
inline bool should_inject_selbstabholer_shipping(const nlohmann::json& items) {
|
|
if (has_versandposition(items)) return false;
|
|
return has_non_return_sale_items(items);
|
|
}
|
|
|
|
inline std::optional<VersandArtRow> lookup_versand_art(OdbcPool::Connection* c,
|
|
const std::string& shipping_name) {
|
|
auto try_lookup = [&](const std::string& cName) -> std::optional<VersandArtRow> {
|
|
std::vector<Param> ps = {{ParamType::NVarChar, cName, 0}};
|
|
ResultSet rs;
|
|
if (!get_pool().execute(c,
|
|
"SELECT TOP 1 kVersandArt, cName, fPrice, fMwSt "
|
|
"FROM dbo.tVersandArt WHERE cName = ?", ps, rs)
|
|
|| rs.empty() || rs[0].empty()) {
|
|
return std::nullopt;
|
|
}
|
|
VersandArtRow row;
|
|
row.kVersandArt = std::stoi(rs[0][0].str);
|
|
row.cName = rs[0][1].str;
|
|
row.fPrice = rs[0].size() > 2 ? to_number(nlohmann::json(rs[0][2].str), 0) : 0;
|
|
row.fMwSt = rs[0].size() > 3 ? to_number(nlohmann::json(rs[0][3].str), 19) : 19;
|
|
return row;
|
|
};
|
|
|
|
std::string name = shipping_name;
|
|
if (name.empty()) name = "Selbstabholer";
|
|
if (auto row = try_lookup(name)) return row;
|
|
if (name != "Selbstabholer") return try_lookup("Selbstabholer");
|
|
return std::nullopt;
|
|
}
|
|
|
|
inline nlohmann::json synthetic_shipping_item(const VersandArtRow& versand_art) {
|
|
const double vat = versand_art.fMwSt;
|
|
const double gross = versand_art.fPrice;
|
|
const double net = gross / (1 + vat / 100);
|
|
return {
|
|
{"type", std::to_string(VERSANDPOSITION_TYPE)},
|
|
{"quantity", "1"},
|
|
{"name", versand_art.cName},
|
|
{"priceGross", std::to_string(gross)},
|
|
{"priceNet", std::to_string(net)},
|
|
{"vat", std::to_string(vat)},
|
|
{"isReturn", "0"}
|
|
};
|
|
}
|
|
|
|
inline nlohmann::json resolve_zahlungsart(OdbcPool::Connection* c, const std::string& name) {
|
|
const std::string lookup_name = name.empty() ? "Bar" : name;
|
|
const std::string cache_key = [&]() {
|
|
std::string key = lookup_name;
|
|
std::transform(key.begin(), key.end(), key.begin(),
|
|
[](unsigned char ch) { return static_cast<char>(std::tolower(ch)); });
|
|
return key;
|
|
}();
|
|
auto it = g_zahlungsart_cache.find(cache_key);
|
|
if (it != g_zahlungsart_cache.end()) return it->second;
|
|
|
|
auto fetch_row = [&](const char* sql) -> std::optional<std::pair<int, std::string>> {
|
|
std::vector<Param> ps = {{ParamType::NVarChar, lookup_name, 0}};
|
|
ResultSet rs;
|
|
if (!get_pool().execute(c, sql, ps, rs) || rs.empty() || rs[0].empty()) {
|
|
return std::nullopt;
|
|
}
|
|
return std::make_pair(std::stoi(rs[0][0].str), rs[0][1].str);
|
|
};
|
|
|
|
std::optional<std::pair<int, std::string>> row =
|
|
fetch_row("SELECT TOP 1 kZahlungsart, cName FROM dbo.tZahlungsart WHERE cName = ?");
|
|
if (!row) {
|
|
row = fetch_row(
|
|
"SELECT TOP 1 z.kZahlungsart, z.cName "
|
|
"FROM dbo.tZahlungsArtSprache zs "
|
|
"INNER JOIN dbo.tZahlungsart z ON z.kZahlungsart = zs.kZahlungsart "
|
|
"WHERE zs.cName = ?");
|
|
}
|
|
if (!row) {
|
|
row = fetch_row(
|
|
"SELECT TOP 1 kZahlungsart, cName FROM dbo.tZahlungsart WHERE UPPER(cName) = UPPER(?)");
|
|
}
|
|
|
|
nlohmann::json zahlungsart;
|
|
if (row) {
|
|
zahlungsart = {{"kZahlungsart", row->first}, {"cName", row->second}};
|
|
} else {
|
|
int kZahlungsart = allocate_pk(c, "tZahlungsart");
|
|
const char* insert =
|
|
"INSERT INTO dbo.tZahlungsart"
|
|
" (kZahlungsart, cName, cPrtString, nLastschrift, cPrtStringVor, cPaymentOption, cKonto,"
|
|
" nAusliefernVorZahlung, nPrioritaet, nMahnwesenAktiv, fSkontoWert, nSkontoZeitraum,"
|
|
" nMatchingOptionen, nIstStandard, nAktiv)"
|
|
" VALUES (?, ?, '', 0, '', '', '', 0, 0, 0, 0, 0, 0, 0, 1)";
|
|
std::vector<Param> ps2 = {
|
|
{ParamType::Int, "", kZahlungsart},
|
|
{ParamType::NVarChar, lookup_name, 0}
|
|
};
|
|
ResultSet rs;
|
|
get_pool().execute(c, insert, ps2, rs);
|
|
zahlungsart = {{"kZahlungsart", kZahlungsart}, {"cName", lookup_name}};
|
|
}
|
|
|
|
g_zahlungsart_cache[cache_key] = zahlungsart;
|
|
return zahlungsart;
|
|
}
|
|
|
|
inline std::string next_customer_number(OdbcPool::Connection* c) {
|
|
std::tm date;
|
|
{
|
|
auto now = std::chrono::system_clock::now();
|
|
std::time_t t = std::chrono::system_clock::to_time_t(now);
|
|
localtime_r(&t, &date);
|
|
}
|
|
return next_number_from_sequence(c, g_config.customerNumberSequence, date);
|
|
}
|
|
|
|
inline std::pair<int,int> lookup_kassenkunde(OdbcPool::Connection* c, const Defaults& defaults) {
|
|
ResultSet rs;
|
|
get_pool().execute(c,
|
|
"SELECT TOP 1 kKunde, kKundenGruppe FROM dbo.tKunde WHERE cKassenKunde = 'Y' ORDER BY kKunde", rs);
|
|
if (!rs.empty() && !rs[0].empty()) {
|
|
int kKunde = std::stoi(rs[0][0].str);
|
|
int grp = defaults.kKundengruppe;
|
|
if (rs[0].size() > 1 && !rs[0][1].str.empty()) grp = std::stoi(rs[0][1].str);
|
|
return {kKunde, grp};
|
|
}
|
|
return {0, defaults.kKundengruppe};
|
|
}
|
|
|
|
inline bool is_walk_in_order(const nlohmann::json& order) {
|
|
std::string customer_number = order.value("customerNumber", "");
|
|
if (customer_number.empty() || customer_number == "0") return true;
|
|
|
|
const auto billing = order.value("billingAddress", nlohmann::json::object());
|
|
return billing.value("lastName", "") == "Laufkunde"
|
|
&& 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", "");
|
|
}
|
|
|
|
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;
|
|
std::string iso = a.value("countryIso", "DE");
|
|
std::transform(iso.begin(), iso.end(), iso.begin(), [](unsigned char ch) { return std::toupper(ch); });
|
|
int kKundengruppe = json_int(a, "customerGroupId", 0);
|
|
if (kKundengruppe <= 0) kKundengruppe = defaults.kKundengruppe;
|
|
|
|
const char* sql =
|
|
"DECLARE @returnValue INT;"
|
|
"DECLARE @kunde_daten dbo.TYPE_spkundeInsert;"
|
|
"INSERT INTO @kunde_daten"
|
|
" (kInetKunde, kKundenKategorie, cKundenNr, cFirma, cAnrede, cTitel, cVorname, cName,"
|
|
" cStrasse, cPLZ, cOrt, cLand, cTel, cFax, cEMail, dErstellt, cMobil, fRabatt, cUSTID, cNewsletter,"
|
|
" cZusatz, cEbayName, kBuyer, cAdressZusatz, cGeburtstag, cWWW, cSperre, cPostID, kKundenGruppe,"
|
|
" nZahlungsziel, kSprache, cISO, cBundesland, cHerkunft, cKassenKunde, cHRNr, kZahlungsart,"
|
|
" nDebitorennr, cSteuerNr, nKreditlimit, kKundenDrucktext, nMahnstopp, nMahnrhythmus, kFirma,"
|
|
" fProvision, nVertreter, fSkonto, nSkontoInTagen)"
|
|
" VALUES"
|
|
" (0, 0, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, GETDATE(), ?, ?, NULL, 'N',"
|
|
" N'', N'', 0, ?, ?, N'', 'N', NULL, ?,"
|
|
" 0, ?, ?, ?, ?, ?, N'', 0,"
|
|
" ?, N'', 0, 0, 0, 0, 0,"
|
|
" NULL, 0, 0, 0);"
|
|
"EXEC @returnValue = Kunde.spKundeInsert @daten = @kunde_daten;"
|
|
"SELECT @returnValue AS kKunde";
|
|
|
|
std::vector<Param> ps = {
|
|
{ParamType::NVarChar, customer_number, 0},
|
|
{ParamType::NVarChar, a.value("company", ""), 0},
|
|
{ParamType::NVarChar, a.value("salutation", ""), 0},
|
|
{ParamType::NVarChar, a.value("title", ""), 0},
|
|
{ParamType::NVarChar, a.value("firstName", ""), 0},
|
|
{ParamType::NVarChar, a.value("lastName", "Laufkunde"), 0},
|
|
{ParamType::NVarChar, a.value("street", "-"), 0},
|
|
{ParamType::NVarChar, a.value("zipCode", ""), 0},
|
|
{ParamType::NVarChar, a.value("city", "-"), 0},
|
|
{ParamType::NVarChar, country_name(iso), 0},
|
|
{ParamType::NVarChar, a.value("phone", ""), 0},
|
|
{ParamType::NVarChar, a.value("fax", ""), 0},
|
|
{ParamType::NVarChar, a.value("email", ""), 0},
|
|
{ParamType::NVarChar, a.value("mobile", ""), 0},
|
|
{ParamType::Double, "", 0, json_double(a, "discount", 0)},
|
|
{ParamType::NVarChar, a.value("addressAddition", ""), 0},
|
|
{ParamType::NVarChar, a.value("birthday", ""), 0},
|
|
{ParamType::Int, "", kKundengruppe},
|
|
{ParamType::Int, "", g_config.kSprache},
|
|
{ParamType::NVarChar, iso, 0},
|
|
{ParamType::NVarChar, a.value("state", ""), 0},
|
|
{ParamType::NVarChar, std::string("Kasse"), 0},
|
|
{ParamType::NVarChar, std::string("Y"), 0},
|
|
{ParamType::Int, "", json_int(a, "debtorNumber", 0)}
|
|
};
|
|
ResultSet rs;
|
|
if (!get_pool().execute(c, sql, ps, rs) || rs.empty() || rs[0].empty()) {
|
|
throw std::runtime_error("Kunde.spKundeInsert failed for '" + customer_number + "'");
|
|
}
|
|
int kKunde = std::stoi(rs[0][0].str);
|
|
if (kKunde <= 0) throw std::runtime_error("Kunde.spKundeInsert returned invalid kKunde");
|
|
return {kKunde, kKundengruppe};
|
|
}
|
|
|
|
inline std::pair<int,int> resolve_customer(OdbcPool::Connection* c, const nlohmann::json& order,
|
|
const Defaults& defaults) {
|
|
if (is_walk_in_order(order)) {
|
|
auto [kKunde, grp] = lookup_kassenkunde(c, defaults);
|
|
if (kKunde > 0) return {kKunde, grp};
|
|
return create_customer(c, next_customer_number(c),
|
|
order.value("billingAddress", nlohmann::json::object()), defaults);
|
|
}
|
|
|
|
std::string customer_number = order.value("customerNumber", "");
|
|
std::vector<Param> ps = {{ParamType::NVarChar, customer_number, 0}};
|
|
ResultSet rs;
|
|
get_pool().execute(c, "SELECT TOP 1 kKunde, kKundenGruppe FROM dbo.tKunde WHERE cKundenNr = ?", ps, rs);
|
|
if (!rs.empty() && !rs[0].empty()) {
|
|
int kKunde = std::stoi(rs[0][0].str);
|
|
int grp = defaults.kKundengruppe;
|
|
if (rs[0].size() > 1 && !rs[0][1].str.empty()) grp = std::stoi(rs[0][1].str);
|
|
return {kKunde, grp};
|
|
}
|
|
return create_customer(c, customer_number, order.value("billingAddress", nlohmann::json::object()), defaults);
|
|
}
|
|
|
|
inline std::string next_order_number(OdbcPool::Connection* c, const std::tm& date) {
|
|
return next_number_from_sequence(c, g_config.orderNumberSequence, date);
|
|
}
|
|
|
|
inline void insert_order_address(OdbcPool::Connection* c, int kAuftrag, int kKunde,
|
|
const nlohmann::json& address, int nTyp) {
|
|
const nlohmann::json a = address.is_null() ? nlohmann::json::object() : address;
|
|
std::string iso = a.value("countryIso", "DE");
|
|
std::transform(iso.begin(), iso.end(), iso.begin(), [](unsigned char ch) { return std::toupper(ch); });
|
|
|
|
const char* sql =
|
|
"INSERT INTO Verkauf.tAuftragAdresse"
|
|
" (kAuftrag, kKunde, cFirma, cAnrede, cTitel, cVorname, cName, cStrasse, cPLZ, cOrt, cLand,"
|
|
" cTel, cZusatz, cAdressZusatz, cMobil, cMail, cFax, cBundesland, cISO, nTyp, nZolldokumenteErforderlich)"
|
|
" VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0)";
|
|
|
|
std::vector<Param> ps = {
|
|
{ParamType::Int, "", kAuftrag},
|
|
{ParamType::Int, "", kKunde},
|
|
{ParamType::NVarChar, a.value("company", ""), 0},
|
|
{ParamType::NVarChar, a.value("salutation", ""), 0},
|
|
{ParamType::NVarChar, a.value("title", ""), 0},
|
|
{ParamType::NVarChar, a.value("firstName", ""), 0},
|
|
{ParamType::NVarChar, a.value("lastName", "-"), 0},
|
|
{ParamType::NVarChar, a.value("street", "-"), 0},
|
|
{ParamType::NVarChar, a.value("zipCode", ""), 0},
|
|
{ParamType::NVarChar, a.value("city", "-"), 0},
|
|
{ParamType::NVarChar, country_name(iso), 0},
|
|
{ParamType::NVarChar, a.value("phone", ""), 0},
|
|
{ParamType::NVarChar, a.value("extraAddressLine", ""), 0},
|
|
{ParamType::NVarChar, a.value("addressAddition", ""), 0},
|
|
{ParamType::NVarChar, a.value("mobile", ""), 0},
|
|
{ParamType::NVarChar, a.value("email", ""), 0},
|
|
{ParamType::NVarChar, a.value("fax", ""), 0},
|
|
{ParamType::NVarChar, a.value("state", ""), 0},
|
|
{ParamType::NVarChar, iso, 0},
|
|
{ParamType::Int, "", nTyp}
|
|
};
|
|
ResultSet rs;
|
|
get_pool().execute(c, sql, ps, rs);
|
|
}
|
|
|
|
inline int insert_order_item(OdbcPool::Connection* c, int kAuftrag, const nlohmann::json& item) {
|
|
double vat = json_double(item, "vat", 19);
|
|
double quantity = json_double(item, "quantity", 1);
|
|
double price_gross = json_double(item, "priceGross", 0);
|
|
double price_net = json_double(item, "priceNet", price_gross / (1 + vat / 100));
|
|
double discount = json_double(item, "discountPercent", 0);
|
|
int kSteuerklasse = steuerklasse_for_vat(vat);
|
|
std::string sku = item.value("sku", "");
|
|
const int position_type = is_versandposition(item) ? VERSANDPOSITION_TYPE : json_int(item, "type", 0);
|
|
|
|
int kArtikel = 0;
|
|
bool has_artikel = false;
|
|
if (!sku.empty() && position_type != VERSANDPOSITION_TYPE) {
|
|
std::vector<Param> ps = {{ParamType::NVarChar, sku, 0}};
|
|
ResultSet rs;
|
|
get_pool().execute(c, "SELECT TOP 1 kArtikel FROM dbo.tArtikel WHERE cArtNr = ?", ps, rs);
|
|
if (!rs.empty() && !rs[0].empty()) {
|
|
kArtikel = std::stoi(rs[0][0].str);
|
|
has_artikel = true;
|
|
}
|
|
}
|
|
|
|
const int nType = position_type == VERSANDPOSITION_TYPE ? VERSANDPOSITION_TYPE : (has_artikel ? 1 : 0);
|
|
const int nReserviert = nType == VERSANDPOSITION_TYPE ? 0 : 1;
|
|
|
|
const char* sql =
|
|
"DECLARE @t TABLE ([kAuftragPosition] INT);"
|
|
"INSERT INTO Verkauf.tAuftragPosition"
|
|
" (kArtikel, kAuftrag, cArtNr, nReserviert, cName, cHinweis, fAnzahl, fVkNetto, fMwSt,"
|
|
" cNameStandard, kSteuerklasse, nType, cEinheit, fFaktor, kSteuerschluessel, fRabatt)"
|
|
" OUTPUT inserted.kAuftragPosition INTO @t"
|
|
" VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?,"
|
|
" ?, ?, ?, ?, 1.0, 3, ?);"
|
|
"SELECT kAuftragPosition FROM @t";
|
|
|
|
Param cartnr{ParamType::NVarChar, has_artikel ? sku : std::string(), 0, 0.0, !has_artikel};
|
|
Param pk_art{ParamType::Int, "", kArtikel, 0.0, !has_artikel};
|
|
|
|
std::string name = item.value("name", sku.empty() ? std::string("Position") : sku);
|
|
std::vector<Param> ps = {
|
|
pk_art,
|
|
{ParamType::Int, "", kAuftrag},
|
|
cartnr,
|
|
{ParamType::Int, "", nReserviert},
|
|
{ParamType::NVarChar, name, 0},
|
|
{ParamType::NVarChar, item.value("note", ""), 0},
|
|
{ParamType::Double, "", 0, quantity},
|
|
{ParamType::Double, "", 0, price_net},
|
|
{ParamType::Double, "", 0, vat},
|
|
{ParamType::NVarChar, name, 0},
|
|
{ParamType::Int, "", kSteuerklasse},
|
|
{ParamType::Int, "", nType},
|
|
{ParamType::NVarChar, item.value("unit", ""), 0},
|
|
{ParamType::Double, "", 0, discount}
|
|
};
|
|
ResultSet rs;
|
|
if (!get_pool().execute(c, sql, ps, rs) || rs.empty() || rs[0].empty()) return 0;
|
|
return std::stoi(rs[0][0].str);
|
|
}
|
|
|
|
inline int parse_pos_auftrag_id(const std::string& external_id) {
|
|
if (external_id.empty()) return 0;
|
|
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, 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, 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";
|
|
|
|
ResultSet rs;
|
|
std::vector<Param> ps = {
|
|
{ParamType::Int, "", kPosAuftrag},
|
|
{ParamType::Int, "", kShopSubShop},
|
|
};
|
|
if (!get_pool().execute(c, sql, ps, rs)
|
|
|| rs.empty() || rs[0].empty() || rs[0][0].type == CellType::Null) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
inline void upsert_pos_order_mapping(OdbcPool::Connection* c, int kAuftrag, int kPosAuftrag) {
|
|
int kShopSubShop = get_active_shop_subshop_id();
|
|
if (kPosAuftrag <= 0 || kShopSubShop <= 0) return;
|
|
|
|
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> ins_ps = {
|
|
{ParamType::Int, "", kAuftrag},
|
|
{ParamType::Int, "", kPosAuftrag},
|
|
{ParamType::Int, "", kShopSubShop},
|
|
};
|
|
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,
|
|
const std::string& external_id) {
|
|
int kPosAuftragPosition = 0;
|
|
try { kPosAuftragPosition = std::stoi(external_id); } catch (...) { return; }
|
|
int kShopSubShop = get_active_shop_subshop_id();
|
|
if (kAuftragPosition <= 0 || kPosAuftragPosition <= 0 || kShopSubShop <= 0) return;
|
|
|
|
const char* sql = "INSERT INTO Pos.tAuftragPositionMapping (kAuftragPosition, kPosAuftragPosition, kShopSubShop) VALUES (?, ?, ?)";
|
|
std::vector<Param> ps = {
|
|
{ParamType::Int, "", kAuftragPosition},
|
|
{ParamType::Int, "", kPosAuftragPosition},
|
|
{ParamType::Int, "", kShopSubShop}
|
|
};
|
|
ResultSet rs;
|
|
get_pool().execute(c, sql, ps, rs);
|
|
}
|
|
|
|
inline bool is_order_delivered(const nlohmann::json& order) {
|
|
auto it = order.find("settings");
|
|
if (it != order.end() && !it->is_null()) {
|
|
auto del = it->find("deliver");
|
|
if (del != it->end()) {
|
|
if (del->is_boolean()) return del->get<bool>();
|
|
if (del->is_number()) return del->get<int>() != 0;
|
|
if (del->is_string()) {
|
|
std::string s = *del;
|
|
std::transform(s.begin(), s.end(), s.begin(), [](unsigned char ch) { return std::tolower(ch); });
|
|
return s == "true" || s == "1";
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// ODBC maps ? placeholders to @p1, @p2, … — never DECLARE @p1 in the same batch.
|
|
inline void recalculate_auftrag_eckdaten(OdbcPool::Connection* c, int kAuftrag) {
|
|
const char* sql =
|
|
"DECLARE @eckdaten_calc Verkauf.TYPE_spAuftragEckdatenBerechnen;"
|
|
"INSERT INTO @eckdaten_calc VALUES (?);"
|
|
"EXEC Verkauf.spAuftragEckdatenBerechnen @auftrag = @eckdaten_calc";
|
|
std::vector<Param> ps = {{ParamType::Int, "", kAuftrag}};
|
|
ResultSet rs;
|
|
if (!get_pool().execute(c, sql, ps, rs)) {
|
|
throw std::runtime_error("spAuftragEckdatenBerechnen failed for kAuftrag="
|
|
+ std::to_string(kAuftrag));
|
|
}
|
|
}
|
|
|
|
inline std::optional<double> get_offener_auftragswert(OdbcPool::Connection* c, int kAuftrag) {
|
|
const char* sql =
|
|
"SELECT ROUND(tAuftragEckdaten.fOffenerWertOhneStorno, 2) AS fOffenerAuftragswert "
|
|
"FROM Verkauf.tAuftrag "
|
|
"LEFT JOIN Verkauf.tAuftragEckdaten ON tAuftragEckdaten.kAuftrag = tAuftrag.kAuftrag "
|
|
"WHERE tAuftrag.kAuftrag = ?";
|
|
std::vector<Param> ps = {{ParamType::Int, "", kAuftrag}};
|
|
ResultSet rs;
|
|
if (!get_pool().execute(c, sql, ps, rs) || rs.empty() || rs[0].empty()
|
|
|| rs[0][0].type == CellType::Null) {
|
|
return std::nullopt;
|
|
}
|
|
return to_number(nlohmann::json(rs[0][0].str), 0);
|
|
}
|
|
|
|
inline bool is_new_payment(const nlohmann::json& payment) {
|
|
return json_int(payment, "paymentId", 0) <= 0;
|
|
}
|
|
|
|
inline void insert_payment(OdbcPool::Connection* c, int kAuftrag, const nlohmann::json& payment,
|
|
const nlohmann::json& order, const std::tm& order_date_tm) {
|
|
std::string payment_name = payment.value("paymentMethodName", "");
|
|
if (payment_name.empty()) payment_name = order.value("paymentMethodName", "Bar");
|
|
nlohmann::json zahlungsart = resolve_zahlungsart(c, payment_name);
|
|
int kZahlung = allocate_pk(c, "tZahlung");
|
|
|
|
recalculate_auftrag_eckdaten(c, kAuftrag);
|
|
auto fOffenerWert = get_offener_auftragswert(c, kAuftrag);
|
|
if (!fOffenerWert) {
|
|
throw std::runtime_error("no open order amount for kAuftrag=" + std::to_string(kAuftrag));
|
|
}
|
|
|
|
const char* sql =
|
|
"IF EXISTS ("
|
|
" SELECT 1 FROM Verkauf.tAuftragEckdaten"
|
|
" WHERE kAuftrag = ?"
|
|
" AND ROUND(fOffenerWertOhneStorno, 2) = ROUND(?, 2)"
|
|
")"
|
|
"BEGIN"
|
|
" INSERT INTO dbo.tZahlung"
|
|
" (kZahlung, cName, dDatum, fBetrag, kBestellung, kBenutzer, nAnzahlung, cHinweis, kZahlungsart,"
|
|
" nKeinExport, cExternalTransactionId, nZuweisungstyp, nZahlungstyp, cZuweisungsinfo, nZuweisungswertung)"
|
|
" VALUES (?, ?, ?, ?, ?, ?, 0, '', ?,"
|
|
" 0, ?, 0, ?, '', 0);"
|
|
"END;"
|
|
"SELECT @@ROWCOUNT AS inserted";
|
|
|
|
std::vector<Param> ps = {
|
|
{ParamType::Int, "", kAuftrag},
|
|
{ParamType::Double, "", 0, *fOffenerWert},
|
|
{ParamType::Int, "", kZahlung},
|
|
{ParamType::NVarChar, zahlungsart.value("cName", ""), 0},
|
|
{ParamType::NVarChar, order_date_sql(order_date_tm), 0},
|
|
{ParamType::Double, "", 0, json_double(payment, "amount", 0)},
|
|
{ParamType::Int, "", kAuftrag},
|
|
{ParamType::Int, "", g_config.kBenutzer},
|
|
{ParamType::Int, "", json_int(zahlungsart, "kZahlungsart", 0)},
|
|
{ParamType::NVarChar, order.value("externalOrderNumber", ""), 0},
|
|
{ParamType::Int, "", ZAHLUNG_TYPE_ZAHLUNG},
|
|
};
|
|
ResultSet rs;
|
|
if (!get_pool().execute(c, sql, ps, rs) || rs.empty() || rs[0].empty()
|
|
|| std::stoi(rs[0][0].str) == 0) {
|
|
throw std::runtime_error("payment insert skipped: open amount changed for kAuftrag="
|
|
+ std::to_string(kAuftrag));
|
|
}
|
|
}
|
|
|
|
inline nlohmann::json create_order(const nlohmann::json& order) {
|
|
load_config();
|
|
|
|
auto guard = get_pool().checkout();
|
|
if (!guard) throw std::runtime_error("no ODBC connection available");
|
|
|
|
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, external_order_number)) {
|
|
return {
|
|
{"orderId", std::to_string(existing->first)},
|
|
{"orderNumber", existing->second},
|
|
{"alreadyExists", true}
|
|
};
|
|
}
|
|
}
|
|
|
|
const Defaults& defaults = get_defaults(c);
|
|
|
|
if (!get_pool().begin(c)) throw std::runtime_error("failed to begin transaction");
|
|
|
|
try {
|
|
g_zahlungsart_cache.clear();
|
|
|
|
std::string creation_date = order.value("creationDate", "");
|
|
std::tm order_date_tm = creation_date.empty()
|
|
? []() {
|
|
std::tm tm{};
|
|
auto now = std::chrono::system_clock::now();
|
|
std::time_t t = std::chrono::system_clock::to_time_t(now);
|
|
localtime_r(&t, &tm);
|
|
return tm;
|
|
}()
|
|
: parse_order_date(creation_date);
|
|
|
|
auto [kKunde, kKundengruppe] = resolve_customer(c, order, defaults);
|
|
nlohmann::json zahlungsart = resolve_zahlungsart(c, order.value("paymentMethodName", "Bar"));
|
|
std::string cAuftragsNr = next_order_number(c, order_date_tm);
|
|
|
|
nlohmann::json order_items = order.contains("orderItems") ? order["orderItems"] : nlohmann::json::array();
|
|
if (!order_items.is_array()) order_items = nlohmann::json::array();
|
|
|
|
auto versand_art = lookup_versand_art(c, order.value("shippingName", ""));
|
|
int kVersandArt = versand_art ? versand_art->kVersandArt : defaults.kVersandArt;
|
|
if (should_inject_selbstabholer_shipping(order_items) && versand_art) {
|
|
order_items.push_back(synthetic_shipping_item(*versand_art));
|
|
}
|
|
|
|
int active_shop = get_active_shop_id();
|
|
const int nIstReadOnly = resolve_n_ist_readonly(order);
|
|
const int nIstExterneRechnung = resolve_n_ist_externe_rechnung(order);
|
|
|
|
const char* insert_auftrag =
|
|
"DECLARE @t TABLE ([kAuftrag] INT);"
|
|
"INSERT INTO Verkauf.tAuftrag"
|
|
" (cAuftragsNr, dErstellt, nKomplettAusgeliefert, kBenutzer, kKunde, kBenutzerErstellt, nType, fFaktor,"
|
|
" kFirmaHistory, kSprache, cVersandlandWaehrung, fVersandlandWaehrungFaktor, fFinanzierungskosten,"
|
|
" cWaehrung, kPlattform, kShop, cKundenNr, cVersandlandISO, kVersandArt, kZahlungsart, kKundengruppe,"
|
|
" 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");
|
|
std::transform(shipping_iso.begin(), shipping_iso.end(), shipping_iso.begin(),
|
|
[](unsigned char ch) { return std::toupper(ch); });
|
|
|
|
std::string currency_iso = order.value("currencyIso", "EUR");
|
|
std::string cKundenNr = resolve_auftrag_c_kunden_nr(order);
|
|
std::vector<Param> ps = {
|
|
{ParamType::NVarChar, cAuftragsNr, 0},
|
|
{ParamType::NVarChar, order_date_sql(order_date_tm), 0},
|
|
{ParamType::Int, "", g_config.kBenutzer},
|
|
{ParamType::Int, "", kKunde},
|
|
{ParamType::Int, "", g_config.kBenutzer},
|
|
{ParamType::Int, "", defaults.kFirmaHistory},
|
|
{ParamType::Int, "", g_config.kSprache},
|
|
{ParamType::NVarChar, currency_iso, 0},
|
|
{ParamType::NVarChar, currency_iso, 0},
|
|
{ParamType::Int, "", defaults.kPlattform},
|
|
{ParamType::Int, "", active_shop == 0 ? -1 : active_shop},
|
|
{ParamType::NVarChar, cKundenNr, 0},
|
|
{ParamType::NVarChar, shipping_iso, 0},
|
|
{ParamType::Int, "", kVersandArt},
|
|
{ParamType::Int, "", json_int(zahlungsart, "kZahlungsart", 0)},
|
|
{ParamType::Int, "", kKundengruppe},
|
|
{ParamType::NVarChar, order.value("externalOrderNumber", ""), 0},
|
|
{ParamType::Int, "", nIstExterneRechnung},
|
|
{ParamType::NVarChar, "Y", 0},
|
|
{ParamType::Int, "", nIstReadOnly},
|
|
};
|
|
if (active_shop == 0) {
|
|
ps[11] = Param::null_int();
|
|
}
|
|
|
|
ResultSet rs;
|
|
if (!get_pool().execute(c, insert_auftrag, ps, rs) || rs.empty() || rs[0].empty()) {
|
|
throw std::runtime_error("failed to insert Verkauf.tAuftrag");
|
|
}
|
|
int kAuftrag = std::stoi(rs[0][0].str);
|
|
|
|
upsert_pos_order_mapping(c, kAuftrag, kPosAuftrag);
|
|
insert_order_address(c, kAuftrag, kKunde, order.value("shippingAddress", nlohmann::json::object()), 0);
|
|
insert_order_address(c, kAuftrag, kKunde, order.value("billingAddress", nlohmann::json::object()), 1);
|
|
|
|
std::vector<delivery::DeliveredItem> delivered_items;
|
|
for (const auto& item : order_items) {
|
|
int kAuftragPosition = insert_order_item(c, kAuftrag, item);
|
|
const std::string external_id = item.value("externalId", "");
|
|
if (!external_id.empty()) {
|
|
insert_pos_order_position_mapping(c, kAuftragPosition, external_id);
|
|
}
|
|
if (kAuftragPosition > 0 && !is_versandposition(item)) {
|
|
delivered_items.push_back({kAuftragPosition, json_double(item, "quantity", 1)});
|
|
}
|
|
}
|
|
|
|
const nlohmann::json& payments = order.contains("payments") ? order["payments"] : nlohmann::json::array();
|
|
for (const auto& payment : payments) {
|
|
if (!is_new_payment(payment)) continue;
|
|
insert_payment(c, kAuftrag, payment, order, order_date_tm);
|
|
}
|
|
|
|
if (is_order_delivered(order)) {
|
|
delivery::deliver_order(c, g_config.kBenutzer, kAuftrag, kVersandArt, delivered_items);
|
|
}
|
|
|
|
const char* calc =
|
|
"DECLARE @auftrag_calc Verkauf.TYPE_spAuftragEckdatenBerechnen;"
|
|
"INSERT INTO @auftrag_calc VALUES (?);"
|
|
"EXEC Verkauf.spAuftragEckdatenBerechnen @auftrag = @auftrag_calc";
|
|
std::vector<Param> ps_calc = {{ParamType::Int, "", kAuftrag}};
|
|
get_pool().execute(c, calc, ps_calc, rs);
|
|
|
|
if (!get_pool().commit(c)) throw std::runtime_error("failed to commit transaction");
|
|
|
|
return {
|
|
{"orderId", std::to_string(kAuftrag)},
|
|
{"orderNumber", cAuftragsNr},
|
|
{"alreadyExists", false}
|
|
};
|
|
} catch (const std::exception&) {
|
|
try { get_pool().rollback(c); } catch (...) {}
|
|
throw;
|
|
}
|
|
}
|
|
|
|
} // namespace order
|