pfand for cpp
This commit is contained in:
239
jtlsrv-cpp/src/queries/product_attributes.hpp
Normal file
239
jtlsrv-cpp/src/queries/product_attributes.hpp
Normal file
@@ -0,0 +1,239 @@
|
||||
#pragma once
|
||||
// Port of src/queries/product-attributes.js — article attributes + Pfand deposit fields.
|
||||
|
||||
#include "../db/pool.hpp"
|
||||
#include "../http.hpp"
|
||||
#include "../log.hpp"
|
||||
#include "nlohmann/json.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct ProductAttributeBundle {
|
||||
nlohmann::json attributes = nlohmann::json::array();
|
||||
// null when no Pfand attributes present; otherwise deposit / deposit_name / d_price
|
||||
nlohmann::json deposit = nullptr;
|
||||
};
|
||||
|
||||
struct AttrRow {
|
||||
int64_t article_id = 0;
|
||||
int64_t k_attribut = 0;
|
||||
int k_shop = 0;
|
||||
int n_sortierung = 0;
|
||||
int k_feld_typ = 0;
|
||||
std::string c_gruppe_name;
|
||||
std::string c_attribut_id;
|
||||
std::string c_name;
|
||||
std::string c_wert_varchar;
|
||||
bool has_n_wert_int = false;
|
||||
int n_wert_int = 0;
|
||||
bool has_f_wert_decimal = false;
|
||||
double f_wert_decimal = 0.0;
|
||||
};
|
||||
|
||||
inline std::string join_int64s(const std::vector<int64_t>& ids) {
|
||||
if (ids.empty()) return "NULL";
|
||||
std::ostringstream oss;
|
||||
for (size_t i = 0; i < ids.size(); ++i) {
|
||||
if (i) oss << ',';
|
||||
oss << ids[i];
|
||||
}
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
inline const std::vector<int64_t>& get_pfand_k_attribute_ids() {
|
||||
static std::vector<int64_t> cached;
|
||||
static bool loaded = false;
|
||||
if (loaded) return cached;
|
||||
|
||||
const char* sql =
|
||||
"SELECT a.kAttribut "
|
||||
"FROM dbo.tAttribut a "
|
||||
"INNER JOIN dbo.tAttributSprache s ON s.kAttribut = a.kAttribut AND s.kSprache = 0 "
|
||||
"WHERE a.cGruppeName = 'JTL-POS' "
|
||||
"AND a.cAttributId IN ('Pfandartikel', 'Pfandart (Bezeichnung auf Ausdruck)', 'Pfandbetrag')";
|
||||
|
||||
ResultSet rs;
|
||||
if (!get_pool().execute(sql, rs)) {
|
||||
logc::warn("failed to load Pfand attribute metadata");
|
||||
return cached;
|
||||
}
|
||||
for (auto& row : rs) {
|
||||
if (!row.empty()) {
|
||||
cached.push_back(parse_int64(row[0].str, 0));
|
||||
}
|
||||
}
|
||||
loaded = true;
|
||||
return cached;
|
||||
}
|
||||
|
||||
inline const std::vector<int64_t>& get_active_shop_ids_for_attrs() {
|
||||
static std::vector<int64_t> cached;
|
||||
static bool loaded = false;
|
||||
if (loaded) return cached;
|
||||
|
||||
const char* sql =
|
||||
"WITH ActiveShops AS ("
|
||||
" SELECT ss.kShop, s.kKategorie AS rootKategorie "
|
||||
" FROM dbo.tShopSubshop ss "
|
||||
" INNER JOIN dbo.tShop s ON s.kShop = ss.kShop "
|
||||
" WHERE ss.nGesperrt = 0"
|
||||
") SELECT DISTINCT ash.kShop FROM ActiveShops ash";
|
||||
|
||||
ResultSet rs;
|
||||
if (!get_pool().execute(sql, rs)) {
|
||||
logc::warn("failed to load active shops for attributes");
|
||||
return cached;
|
||||
}
|
||||
for (auto& row : rs) {
|
||||
if (!row.empty()) {
|
||||
cached.push_back(parse_int64(row[0].str, 0));
|
||||
}
|
||||
}
|
||||
loaded = true;
|
||||
return cached;
|
||||
}
|
||||
|
||||
inline nlohmann::json to_pos_attribute(const AttrRow& row) {
|
||||
return {
|
||||
{"aname", row.c_name},
|
||||
{"aprice", "0.0"},
|
||||
{"asort", std::to_string(row.n_sortierung)},
|
||||
{"atype", std::to_string(row.k_feld_typ)},
|
||||
{"agroup", row.c_gruppe_name},
|
||||
};
|
||||
}
|
||||
|
||||
inline nlohmann::json derive_deposit_fields(const std::vector<AttrRow>& rows) {
|
||||
const AttrRow* pfand_artikel = nullptr;
|
||||
const AttrRow* pfand_art = nullptr;
|
||||
const AttrRow* pfand_betrag = nullptr;
|
||||
|
||||
for (const auto& row : rows) {
|
||||
if (row.c_attribut_id == "Pfandartikel") pfand_artikel = &row;
|
||||
else if (row.c_attribut_id == "Pfandart (Bezeichnung auf Ausdruck)") pfand_art = &row;
|
||||
else if (row.c_attribut_id == "Pfandbetrag") pfand_betrag = &row;
|
||||
}
|
||||
|
||||
if (!pfand_artikel && !pfand_art && !pfand_betrag) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string d_price = "0.0";
|
||||
if (pfand_betrag && pfand_betrag->has_f_wert_decimal) {
|
||||
char buf[32];
|
||||
std::snprintf(buf, sizeof(buf), "%.2f", pfand_betrag->f_wert_decimal);
|
||||
d_price = buf;
|
||||
}
|
||||
|
||||
return {
|
||||
{"deposit", (pfand_artikel && pfand_artikel->has_n_wert_int && pfand_artikel->n_wert_int == 1) ? "1" : "0"},
|
||||
{"deposit_name", pfand_art ? pfand_art->c_wert_varchar : ""},
|
||||
{"d_price", d_price},
|
||||
};
|
||||
}
|
||||
|
||||
inline std::vector<AttrRow> merge_article_attributes(std::vector<AttrRow> rows) {
|
||||
// Later rows for the same kAttribut win (shop-specific over global), matching JS Map set order.
|
||||
std::map<int64_t, AttrRow> by_attr;
|
||||
for (auto& row : rows) {
|
||||
by_attr[row.k_attribut] = std::move(row);
|
||||
}
|
||||
std::vector<AttrRow> merged;
|
||||
merged.reserve(by_attr.size());
|
||||
for (auto& [_, row] : by_attr) {
|
||||
merged.push_back(std::move(row));
|
||||
}
|
||||
std::sort(merged.begin(), merged.end(), [](const AttrRow& a, const AttrRow& b) {
|
||||
return a.n_sortierung < b.n_sortierung;
|
||||
});
|
||||
return merged;
|
||||
}
|
||||
|
||||
inline AttrRow parse_attr_row(const Row& row) {
|
||||
AttrRow a;
|
||||
if (row.size() < 11) return a;
|
||||
a.article_id = parse_int64(row[0].str, 0);
|
||||
a.k_attribut = parse_int64(row[1].str, 0);
|
||||
a.k_shop = parse_int(row[2].str, 0);
|
||||
a.n_sortierung = parse_int(row[3].str, 0);
|
||||
a.k_feld_typ = parse_int(row[4].str, 0);
|
||||
a.c_gruppe_name = row[5].type == CellType::Null ? "" : row[5].str;
|
||||
a.c_attribut_id = row[6].type == CellType::Null ? "" : row[6].str;
|
||||
a.c_name = row[7].type == CellType::Null ? "" : row[7].str;
|
||||
a.c_wert_varchar = row[8].type == CellType::Null ? "" : row[8].str;
|
||||
if (row[9].type != CellType::Null) {
|
||||
a.has_n_wert_int = true;
|
||||
a.n_wert_int = parse_int(row[9].str, 0);
|
||||
}
|
||||
if (row[10].type != CellType::Null) {
|
||||
a.has_f_wert_decimal = true;
|
||||
a.f_wert_decimal = parse_double(row[10].str, 0);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
inline std::map<int64_t, ProductAttributeBundle> get_product_attributes(
|
||||
const std::vector<int64_t>& article_ids) {
|
||||
std::map<int64_t, ProductAttributeBundle> out;
|
||||
if (article_ids.empty()) return out;
|
||||
|
||||
const auto& pfand_ids = get_pfand_k_attribute_ids();
|
||||
const auto& active_shops = get_active_shop_ids_for_attrs();
|
||||
|
||||
std::string sql =
|
||||
"SELECT "
|
||||
" aa.kArtikel AS articleId, "
|
||||
" aa.kAttribut, "
|
||||
" aa.kShop, "
|
||||
" at.nSortierung, "
|
||||
" at.kFeldTyp, "
|
||||
" at.cGruppeName, "
|
||||
" at.cAttributId, "
|
||||
" ats.cName, "
|
||||
" aas.cWertVarchar, "
|
||||
" aas.nWertInt, "
|
||||
" aas.fWertDecimal "
|
||||
"FROM dbo.tArtikelAttribut aa "
|
||||
"INNER JOIN dbo.tArtikelAttributSprache aas "
|
||||
" ON aas.kArtikelAttribut = aa.kArtikelAttribut AND aas.kSprache = 0 "
|
||||
"INNER JOIN dbo.tAttribut at ON at.kAttribut = aa.kAttribut "
|
||||
"INNER JOIN dbo.tAttributSprache ats ON ats.kAttribut = at.kAttribut AND ats.kSprache = 0 "
|
||||
"WHERE aa.kArtikel IN (" + join_int64s(article_ids) + ") "
|
||||
" AND ("
|
||||
" aa.kShop = 0 "
|
||||
" OR ("
|
||||
" aa.kShop IN (" + join_int64s(active_shops) + ") "
|
||||
" AND aa.kAttribut IN (" + join_int64s(pfand_ids) + ") "
|
||||
" )"
|
||||
" ) "
|
||||
"ORDER BY aa.kArtikel, at.nSortierung, aa.kShop";
|
||||
|
||||
ResultSet rs;
|
||||
if (!get_pool().execute(sql, rs)) {
|
||||
logc::warn("product attributes query failed (%zu articles)", article_ids.size());
|
||||
return out;
|
||||
}
|
||||
|
||||
std::map<int64_t, std::vector<AttrRow>> rows_by_article;
|
||||
for (const auto& row : rs) {
|
||||
AttrRow a = parse_attr_row(row);
|
||||
if (a.article_id == 0) continue;
|
||||
rows_by_article[a.article_id].push_back(std::move(a));
|
||||
}
|
||||
|
||||
for (auto& [article_id, rows] : rows_by_article) {
|
||||
auto merged = merge_article_attributes(std::move(rows));
|
||||
ProductAttributeBundle bundle;
|
||||
for (const auto& row : merged) {
|
||||
bundle.attributes.push_back(to_pos_attribute(row));
|
||||
}
|
||||
bundle.deposit = derive_deposit_fields(merged);
|
||||
out[article_id] = std::move(bundle);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
Reference in New Issue
Block a user