u
This commit is contained in:
2
API.md
2
API.md
@@ -296,7 +296,7 @@ No `limit` — returns all groups newer than the cursor.
|
|||||||
|
|
||||||
### `GET /v1/customer`
|
### `GET /v1/customer`
|
||||||
|
|
||||||
Customer deltas. Only customers of customer group `1` are returned, scoped to the active shop (`vCustomer.kShop = active kShop`) and its subshop (`tInetKundeShop.kSubShop = active kShopSubshop`). Rows are ordered by `bLastChanged` ascending, and only rows with `bLastChanged <= nMaxLastChanged` of the active subshop are returned.
|
Customer deltas. Customers are scoped to the active shop (`vCustomer.kShop = active kShop`) and its subshop (`tInetKundeShop.kSubShop = active kShopSubshop`). Rows are ordered by `bLastChanged` ascending, and only rows with `bLastChanged > lastChangedCustomer` are returned (matching the count semantics of `/v1/init`).
|
||||||
|
|
||||||
| Param | Default |
|
| Param | Default |
|
||||||
|---|---|
|
|---|---|
|
||||||
|
|||||||
@@ -9,8 +9,7 @@ void handle_customer(HttpRequest& req, HttpResponse& resp, RouteContext& /*ctx*/
|
|||||||
int limit = req.get_query_int("limit", 20);
|
int limit = req.get_query_int("limit", 20);
|
||||||
int shop = get_active_shop_id();
|
int shop = get_active_shop_id();
|
||||||
int subshop = get_active_shop_subshop_id();
|
int subshop = get_active_shop_subshop_id();
|
||||||
int64_t max_last_changed = get_active_max_last_changed();
|
|
||||||
|
|
||||||
auto customers = get_customer_list(cursor, limit, shop, subshop, max_last_changed);
|
auto customers = get_customer_list(cursor, limit, shop, subshop);
|
||||||
resp.send_json(200, customers);
|
resp.send_json(200, customers);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,8 +43,7 @@ static const char* CUSTOMER_COUNT_SQL =
|
|||||||
"SELECT COUNT(DISTINCT k.kKunde) AS cnt FROM dbo.tkunde k "
|
"SELECT COUNT(DISTINCT k.kKunde) AS cnt FROM dbo.tkunde k "
|
||||||
"LEFT JOIN dbo.tInetKundeShop iks ON iks.kKunde = k.kKunde "
|
"LEFT JOIN dbo.tInetKundeShop iks ON iks.kKunde = k.kKunde "
|
||||||
"AND iks.kShop = ? AND iks.kSubShop = ? "
|
"AND iks.kShop = ? AND iks.kSubShop = ? "
|
||||||
"WHERE k.kKundenGruppe = 1 "
|
"WHERE (? = 0 OR EXISTS (SELECT 1 FROM dbo.tInetKundeShop x "
|
||||||
"AND (? = 0 OR EXISTS (SELECT 1 FROM dbo.tInetKundeShop x "
|
|
||||||
"WHERE x.kKunde = k.kKunde AND x.kShop = ?)) "
|
"WHERE x.kKunde = k.kKunde AND x.kShop = ?)) "
|
||||||
"AND CONVERT(BIGINT, k.bRowversion) > ?";
|
"AND CONVERT(BIGINT, k.bRowversion) > ?";
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "../db/pool.hpp"
|
#include "../db/pool.hpp"
|
||||||
|
#include "../log.hpp"
|
||||||
#include "nlohmann/json.hpp"
|
#include "nlohmann/json.hpp"
|
||||||
#include "shop.hpp"
|
#include "shop.hpp"
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
static const char* CUSTOMER_LIST_SQL =
|
static const char* CUSTOMER_LIST_SQL =
|
||||||
"SELECT TOP (?) "
|
"SELECT TOP (?) "
|
||||||
@@ -37,8 +39,6 @@ static const char* CUSTOMER_LIST_SQL =
|
|||||||
" AND tInetKundeShop.kSubShop = ? "
|
" AND tInetKundeShop.kSubShop = ? "
|
||||||
"WHERE vCustomer.kShop = ? "
|
"WHERE vCustomer.kShop = ? "
|
||||||
" AND CONVERT(BIGINT, vCustomer.bLastChanged) > ? "
|
" AND CONVERT(BIGINT, vCustomer.bLastChanged) > ? "
|
||||||
" AND CONVERT(BIGINT, vCustomer.bLastChanged) <= ? "
|
|
||||||
" AND vCustomer.kCustomerGroupId = 1 "
|
|
||||||
"ORDER BY vCustomer.bLastChanged ASC";
|
"ORDER BY vCustomer.bLastChanged ASC";
|
||||||
|
|
||||||
inline std::string format_birthday(const std::string& c_date_of_birth) {
|
inline std::string format_birthday(const std::string& c_date_of_birth) {
|
||||||
@@ -56,13 +56,12 @@ inline std::string format_birthday(const std::string& c_date_of_birth) {
|
|||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline nlohmann::json get_customer_list(int64_t cursor, int limit, int shop, int subshop, int64_t max_last_changed) {
|
inline nlohmann::json get_customer_list(int64_t cursor, int limit, int shop, int subshop) {
|
||||||
std::vector<Param> ps = {
|
std::vector<Param> ps = {
|
||||||
{ParamType::Int, "", limit},
|
{ParamType::Int, "", limit},
|
||||||
{ParamType::Int, "", subshop},
|
{ParamType::Int, "", subshop},
|
||||||
{ParamType::Int, "", shop},
|
{ParamType::Int, "", shop},
|
||||||
{ParamType::BigInt, "", cursor},
|
{ParamType::BigInt, "", cursor},
|
||||||
{ParamType::BigInt, "", max_last_changed},
|
|
||||||
};
|
};
|
||||||
ResultSet rs;
|
ResultSet rs;
|
||||||
if (!get_pool().execute(CUSTOMER_LIST_SQL, ps, rs)) {
|
if (!get_pool().execute(CUSTOMER_LIST_SQL, ps, rs)) {
|
||||||
|
|||||||
@@ -11,8 +11,7 @@ LEFT JOIN dbo.tInetKundeShop iks
|
|||||||
ON iks.kKunde = k.kKunde
|
ON iks.kKunde = k.kKunde
|
||||||
AND iks.kShop = @kShop
|
AND iks.kShop = @kShop
|
||||||
AND iks.kSubShop = @SubShopId
|
AND iks.kSubShop = @SubShopId
|
||||||
WHERE k.kKundenGruppe = 1
|
WHERE (@kShop = 0 OR EXISTS (
|
||||||
AND (@kShop = 0 OR EXISTS (
|
|
||||||
SELECT 1 FROM dbo.tInetKundeShop x
|
SELECT 1 FROM dbo.tInetKundeShop x
|
||||||
WHERE x.kKunde = k.kKunde AND x.kShop = @kShop
|
WHERE x.kKunde = k.kKunde AND x.kShop = @kShop
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import sql from 'mssql';
|
|||||||
import { isDemoMode } from '../demo/mode.js';
|
import { isDemoMode } from '../demo/mode.js';
|
||||||
import { getDemoCustomerList } from '../demo/store.js';
|
import { getDemoCustomerList } from '../demo/store.js';
|
||||||
import { getPool } from '../db.js';
|
import { getPool } from '../db.js';
|
||||||
import { getActiveMaxLastChanged, getActiveShopId, getActiveShopSubshopId } from '../shop.js';
|
import { getActiveShopId, getActiveShopSubshopId } from '../shop.js';
|
||||||
|
|
||||||
const CUSTOMER_LIST_SQL = `
|
const CUSTOMER_LIST_SQL = `
|
||||||
SELECT TOP (@Limit)
|
SELECT TOP (@Limit)
|
||||||
@@ -38,8 +38,6 @@ LEFT JOIN dbo.tInetKundeShop
|
|||||||
AND tInetKundeShop.kSubShop = @SubShopId
|
AND tInetKundeShop.kSubShop = @SubShopId
|
||||||
WHERE vCustomer.kShop = @ShopId
|
WHERE vCustomer.kShop = @ShopId
|
||||||
AND CONVERT(BIGINT, vCustomer.bLastChanged) > @bLastChanged
|
AND CONVERT(BIGINT, vCustomer.bLastChanged) > @bLastChanged
|
||||||
AND CONVERT(BIGINT, vCustomer.bLastChanged) <= @MaxLastChanged
|
|
||||||
AND vCustomer.kCustomerGroupId = 1
|
|
||||||
ORDER BY vCustomer.bLastChanged ASC;
|
ORDER BY vCustomer.bLastChanged ASC;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -72,7 +70,6 @@ export async function getCustomerList({ cursor = 0, limit = 20 } = {}) {
|
|||||||
.input('ShopId', sql.Int, getActiveShopId())
|
.input('ShopId', sql.Int, getActiveShopId())
|
||||||
.input('SubShopId', sql.Int, getActiveShopSubshopId())
|
.input('SubShopId', sql.Int, getActiveShopSubshopId())
|
||||||
.input('bLastChanged', sql.BigInt, cursor)
|
.input('bLastChanged', sql.BigInt, cursor)
|
||||||
.input('MaxLastChanged', sql.BigInt, getActiveMaxLastChanged())
|
|
||||||
.query(CUSTOMER_LIST_SQL);
|
.query(CUSTOMER_LIST_SQL);
|
||||||
|
|
||||||
return result.recordset.map((row) => ({
|
return result.recordset.map((row) => ({
|
||||||
|
|||||||
@@ -82,6 +82,26 @@ function priceOverridesSql(articleIds) {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the first image hash (ordered by nNr) per article, matching the main
|
||||||
|
// query's imgHash selection. Used to resolve the parent's image for products
|
||||||
|
// without a picture of their own.
|
||||||
|
function imageHashesSql(articleIds) {
|
||||||
|
const idList = articleIds.join(',');
|
||||||
|
return `
|
||||||
|
SELECT articleId, imgHash
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
abp.kArtikel AS articleId,
|
||||||
|
img.cHash AS imgHash,
|
||||||
|
ROW_NUMBER() OVER (PARTITION BY abp.kArtikel ORDER BY abp.nNr) AS rn
|
||||||
|
FROM dbo.tArtikelbildPlattform abp
|
||||||
|
INNER JOIN dbo.tBild img ON img.kBild = abp.kBild
|
||||||
|
WHERE abp.kArtikel IN (${idList})
|
||||||
|
) t
|
||||||
|
WHERE rn = 1 AND imgHash IS NOT NULL AND imgHash <> '';
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
function formatDateTime(date) {
|
function formatDateTime(date) {
|
||||||
if (!date) {
|
if (!date) {
|
||||||
return '0001-01-01 00:00:00';
|
return '0001-01-01 00:00:00';
|
||||||
@@ -118,10 +138,20 @@ export async function getProductList({ cursor = 0, limit = 20 } = {}) {
|
|||||||
|
|
||||||
const overridesByArticle = new Map();
|
const overridesByArticle = new Map();
|
||||||
let attributesByArticle = new Map();
|
let attributesByArticle = new Map();
|
||||||
|
let parentImageHashes = new Map();
|
||||||
if (articleIds.length > 0) {
|
if (articleIds.length > 0) {
|
||||||
const [overrideResult, attributeMap] = await Promise.all([
|
// Include any parent articles referenced by products in this batch so we
|
||||||
|
// can resolve the parent image for products without a picture of their own,
|
||||||
|
// even when the parent itself is not part of the batch.
|
||||||
|
const parentIds = products
|
||||||
|
.map((p) => (p.parentArticleId > 0 && !p.imgHash ? Number(p.parentArticleId) : null))
|
||||||
|
.filter((id) => id !== null && !articleIds.includes(id));
|
||||||
|
const hashIds = [...new Set([...articleIds, ...parentIds])];
|
||||||
|
|
||||||
|
const [overrideResult, attributeMap, imageHashResult] = await Promise.all([
|
||||||
pool.request().query(priceOverridesSql(articleIds)),
|
pool.request().query(priceOverridesSql(articleIds)),
|
||||||
getProductAttributes(pool, articleIds),
|
getProductAttributes(pool, articleIds),
|
||||||
|
pool.request().query(imageHashesSql(hashIds)),
|
||||||
]);
|
]);
|
||||||
attributesByArticle = attributeMap;
|
attributesByArticle = attributeMap;
|
||||||
for (const row of overrideResult.recordset) {
|
for (const row of overrideResult.recordset) {
|
||||||
@@ -130,6 +160,9 @@ export async function getProductList({ cursor = 0, limit = 20 } = {}) {
|
|||||||
}
|
}
|
||||||
overridesByArticle.get(row.articleId).set(row.customerGroupId, row.netPrice);
|
overridesByArticle.get(row.articleId).set(row.customerGroupId, row.netPrice);
|
||||||
}
|
}
|
||||||
|
for (const row of imageHashResult.recordset) {
|
||||||
|
parentImageHashes.set(row.articleId, row.imgHash);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return products.map((product) => {
|
return products.map((product) => {
|
||||||
@@ -151,10 +184,16 @@ export async function getProductList({ cursor = 0, limit = 20 } = {}) {
|
|||||||
const categoryIds = product.categoryIds ? product.categoryIds.split(',') : [];
|
const categoryIds = product.categoryIds ? product.categoryIds.split(',') : [];
|
||||||
const articleAttributes = attributesByArticle.get(product.id);
|
const articleAttributes = attributesByArticle.get(product.id);
|
||||||
|
|
||||||
|
// If a product has a parent and no picture of its own, fall back to the
|
||||||
|
// parent's image hash.
|
||||||
|
const hasOwnImage = !!product.imgHash;
|
||||||
|
const parentHash = !hasOwnImage ? parentImageHashes.get(product.parentArticleId) : null;
|
||||||
|
const imageHash = hasOwnImage ? product.imgHash : parentHash;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_id: String(product.id),
|
_id: String(product.id),
|
||||||
imghash: product.imgHash ?? null,
|
imghash: imageHash ?? null,
|
||||||
imgsrc: product.imgHash ?? null,
|
imgsrc: imageHash ?? null,
|
||||||
sku: product.sku,
|
sku: product.sku,
|
||||||
barcode: product.barcode ?? null,
|
barcode: product.barcode ?? null,
|
||||||
name: product.name,
|
name: product.name,
|
||||||
|
|||||||
Reference in New Issue
Block a user