commit 7d3cd38106c7bc3d0ac6c3455d9b6758ce05ae34
parent a853c303e488e6da1b65b7e6b42c1d1d5d81035e
Author: Christian Grothoff <christian@grothoff.org>
Date: Sun, 28 Dec 2025 14:43:28 +0100
add product groups, pots and net-v-gross status to products in REST API
Diffstat:
13 files changed, 283 insertions(+), 35 deletions(-)
diff --git a/src/backend/taler-merchant-httpd_private-get-products-ID.c b/src/backend/taler-merchant-httpd_private-get-products-ID.c
@@ -135,6 +135,12 @@ TMH_private_get_products_ID (
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_timestamp ("next_restock",
(pd.next_restock))),
+ GNUNET_JSON_pack_uint64 ("product_group_id",
+ pd.product_group_id),
+ GNUNET_JSON_pack_uint64 ("money_pot_id",
+ pd.money_pot_id),
+ GNUNET_JSON_pack_bool ("price_is_net",
+ pd.price_is_net),
GNUNET_JSON_pack_uint64 ("minimum_age",
pd.minimum_age));
TALER_MERCHANTDB_product_details_free (&pd);
diff --git a/src/backend/taler-merchant-httpd_private-patch-products-ID.c b/src/backend/taler-merchant-httpd_private-patch-products-ID.c
@@ -135,6 +135,8 @@ TMH_private_patch_products_ID (
bool lost_reduced;
bool sold_reduced;
bool stock_reduced;
+ bool no_group;
+ bool no_pot;
pd.total_sold = 0; /* will be ignored anyway */
GNUNET_assert (NULL != mi);
@@ -343,7 +345,9 @@ TMH_private_patch_products_ID (
&no_product,
&lost_reduced,
&sold_reduced,
- &stock_reduced);
+ &stock_reduced,
+ &no_group,
+ &no_pot);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
@@ -401,6 +405,24 @@ TMH_private_patch_products_ID (
product_id);
goto cleanup;
}
+ if (no_group)
+ {
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_PRODUCT_GROUP_UNKNOWN,
+ NULL);
+ goto cleanup;
+ }
+ if (no_pot)
+ {
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_MONEY_POT_UNKNOWN,
+ NULL);
+ goto cleanup;
+ }
if (lost_reduced)
{
ret = TALER_MHD_reply_with_error (
diff --git a/src/backend/taler-merchant-httpd_private-post-products.c b/src/backend/taler-merchant-httpd_private-post-products.c
@@ -111,6 +111,18 @@ TMH_private_post_products (const struct TMH_RequestHandler *rh,
GNUNET_JSON_spec_uint32 ("minimum_age",
&pd.minimum_age),
NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_uint64 ("money_pot_id",
+ &pd.money_pot_id),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_uint64 ("product_group_id",
+ &pd.product_group_id),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_bool ("price_is_net",
+ &pd.price_is_net),
+ NULL),
GNUNET_JSON_spec_end ()
};
size_t num_cats = 0;
@@ -118,6 +130,8 @@ TMH_private_post_products (const struct TMH_RequestHandler *rh,
bool conflict;
bool no_instance;
ssize_t no_cat;
+ bool no_group;
+ bool no_pot;
enum GNUNET_DB_QueryStatus qs;
MHD_RESULT ret;
@@ -314,7 +328,9 @@ TMH_private_post_products (const struct TMH_RequestHandler *rh,
cats,
&no_instance,
&conflict,
- &no_cat);
+ &no_cat,
+ &no_group,
+ &no_pot);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
@@ -346,6 +362,24 @@ TMH_private_post_products (const struct TMH_RequestHandler *rh,
mi->settings.id);
goto cleanup;
}
+ if (no_group)
+ {
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_PRODUCT_GROUP_UNKNOWN,
+ NULL);
+ goto cleanup;
+ }
+ if (no_pot)
+ {
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_MONEY_POT_UNKNOWN,
+ NULL);
+ goto cleanup;
+ }
if (conflict)
{
ret = TALER_MHD_reply_with_error (
diff --git a/src/backenddb/pg_insert_product.c b/src/backenddb/pg_insert_product.c
@@ -36,7 +36,9 @@ TMH_PG_insert_product (void *cls,
const uint64_t *cats,
bool *no_instance,
bool *conflict,
- ssize_t *no_cat)
+ ssize_t *no_cat,
+ bool *no_group,
+ bool *no_pot)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
@@ -64,6 +66,13 @@ TMH_PG_insert_product (void *cls,
cats,
pg->conn),
GNUNET_PQ_query_param_string (pd->product_name),
+ (0 == pd->product_group_id)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_uint64 (&pd->product_group_id),
+ (0 == pd->money_pot_id)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_uint64 (&pd->money_pot_id),
+ GNUNET_PQ_query_param_bool (pd->price_is_net),
GNUNET_PQ_query_param_end
};
uint64_t ncat;
@@ -77,6 +86,10 @@ TMH_PG_insert_product (void *cls,
GNUNET_PQ_result_spec_uint64 ("no_cat",
&ncat),
&cats_found),
+ GNUNET_PQ_result_spec_bool ("no_group",
+ no_group),
+ GNUNET_PQ_result_spec_bool ("no_pot",
+ no_pot),
GNUNET_PQ_result_spec_end
};
enum GNUNET_DB_QueryStatus qs;
@@ -88,9 +101,12 @@ TMH_PG_insert_product (void *cls,
" out_conflict AS conflict"
",out_no_instance AS no_instance"
",out_no_cat AS no_cat"
+ ",out_no_group AS no_group"
+ ",out_no_pot AS no_pot"
" FROM merchant_do_insert_product"
"($1, $2, $3, $4::TEXT::JSONB, $5, $6, $7::TEXT::JSONB, $8"
- ",$9, $10, $11, $12, $13, $14::TEXT::JSONB, $15, $16, $17, $18);");
+ ",$9, $10, $11, $12, $13, $14::TEXT::JSONB, $15, $16"
+ ",$17, $18, $19, $20, $21);");
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"insert_product",
params,
diff --git a/src/backenddb/pg_insert_product.h b/src/backenddb/pg_insert_product.h
@@ -38,6 +38,8 @@
* @param[out] conflict set to true if a conflicting
* product already exists in the database
* @param[out] no_cat set to index of non-existing category from @a cats, or -1 if all @a cats were found
+ * @param[out] no_group set to true if the product group in @a pd is unknown
+ * @param[out] no_pot set to true if the money pot in @a pd is unknown
* @return database result code
*/
enum GNUNET_DB_QueryStatus
@@ -49,7 +51,9 @@ TMH_PG_insert_product (void *cls,
const uint64_t *cats,
bool *no_instance,
bool *conflict,
- ssize_t *no_cat);
+ ssize_t *no_cat,
+ bool *no_group,
+ bool *no_pot);
#endif
diff --git a/src/backenddb/pg_insert_product.sql b/src/backenddb/pg_insert_product.sql
@@ -34,9 +34,14 @@ CREATE FUNCTION merchant_do_insert_product (
IN in_minimum_age INT4,
IN ina_categories INT8[],
IN in_product_name TEXT,
+ IN in_product_group_id INT8, -- NULL for default
+ IN in_money_pot_id INT8, -- NULL for none
+ IN in_price_is_net BOOL,
OUT out_no_instance BOOL,
OUT out_conflict BOOL,
- OUT out_no_cat INT8)
+ OUT out_no_cat INT8,
+ OUT out_no_group BOOL,
+ OUT out_no_pot BOOL)
LANGUAGE plpgsql
AS $$
DECLARE
@@ -48,6 +53,9 @@ DECLARE
my_price_array taler_amount_currency[];
BEGIN
+out_no_group = FALSE;
+out_no_pot = FALSE;
+
-- Which instance are we using?
SELECT merchant_serial
INTO my_merchant_id
@@ -63,6 +71,34 @@ THEN
END IF;
out_no_instance=FALSE;
+IF in_product_group_id IS NOT NULL
+THEN
+ PERFORM FROM merchant_product_groups
+ WHERE product_group_serial=in_product_group_id
+ AND merchant_serial=my_merchant_id;
+ IF NOT FOUND
+ THEN
+ out_no_group=TRUE;
+ out_conflict=FALSE;
+ out_no_cat=NULL;
+ RETURN;
+ END IF;
+END IF;
+
+IF in_money_pot_id IS NOT NULL
+THEN
+ PERFORM FROM merchant_money_pots
+ WHERE money_pot_serial=in_money_pot_id
+ AND merchant_serial=my_merchant_id;
+ IF NOT FOUND
+ THEN
+ out_no_pot=TRUE;
+ out_conflict=FALSE;
+ out_no_cat=NULL;
+ RETURN;
+ END IF;
+END IF;
+
IF COALESCE (array_length(ina_price_list,1),0) = 0
THEN
my_price_array := ARRAY[in_price]::taler_amount_currency[];
@@ -89,7 +125,10 @@ INSERT INTO merchant_inventory
,fractional_precision_level
,address
,next_restock
- ,minimum_age
+ ,minimum_age
+ ,product_group_serial
+ ,money_pot_serial
+ ,price_is_net
) VALUES (
my_merchant_id
,in_product_id
@@ -114,7 +153,11 @@ INSERT INTO merchant_inventory
,in_fractional_precision_level
,in_address
,in_next_restock
- ,in_minimum_age)
+ ,in_minimum_age
+ ,in_product_group_id
+ ,in_money_pot_id
+ ,in_price_is_net
+ )
ON CONFLICT (merchant_serial, product_id) DO NOTHING
RETURNING product_serial
INTO my_product_serial;
@@ -143,7 +186,10 @@ THEN
AND fractional_precision_level=in_fractional_precision_level
AND address=in_address
AND next_restock=in_next_restock
- AND minimum_age=in_minimum_age;
+ AND minimum_age=in_minimum_age
+ AND product_group_serial IS NOT DISTINCT FROM in_product_group_id
+ AND money_pot_serial IS NOT DISTINCT FROM in_money_pot_id
+ AND price_is_net=in_price_is_net;
IF NOT FOUND
THEN
out_conflict=TRUE;
diff --git a/src/backenddb/pg_lookup_all_products.c b/src/backenddb/pg_lookup_all_products.c
@@ -124,9 +124,21 @@ lookup_products_cb (void *cls,
"categories",
&num_categories,
&categories),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_uint64 ("product_group_serial",
+ &pd.product_group_id),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_uint64 ("money_pot_serial",
+ &pd.money_pot_id),
+ NULL),
+ GNUNET_PQ_result_spec_bool ("price_is_net",
+ &pd.price_is_net),
GNUNET_PQ_result_spec_end
};
+ pd.product_group_id = 0;
+ pd.money_pot_id = 0;
if (GNUNET_OK !=
GNUNET_PQ_extract_result (result,
rs,
@@ -193,6 +205,9 @@ TMH_PG_lookup_all_products (void *cls,
",product_id"
",product_serial"
",t.category_array AS categories"
+ ",product_group_serial"
+ ",money_pot_serial"
+ ",price_is_net"
" FROM merchant_inventory minv"
" JOIN merchant_instances inst"
" USING (merchant_serial)"
diff --git a/src/backenddb/pg_lookup_product.c b/src/backenddb/pg_lookup_product.c
@@ -51,9 +51,9 @@ TMH_PG_lookup_product (void *cls,
",mi.price"
",mi.price_array"
",mi.taxes::TEXT"
- ",mi.total_stock"
- ",mi.total_stock_frac"
- ",mi.allow_fractional_quantity"
+ ",mi.total_stock"
+ ",mi.total_stock_frac"
+ ",mi.allow_fractional_quantity"
",mi.fractional_precision_level"
",mi.total_sold"
",mi.total_sold_frac"
@@ -63,6 +63,9 @@ TMH_PG_lookup_product (void *cls,
",mi.address::TEXT"
",mi.next_restock"
",mi.minimum_age"
+ ",mi.product_group_serial"
+ ",mi.money_pot_serial"
+ ",mi.price_is_net"
",t.category_array AS categories"
" FROM merchant_inventory mi"
" JOIN merchant_instances inst"
@@ -146,11 +149,23 @@ TMH_PG_lookup_product (void *cls,
"categories",
num_categories,
&my_categories),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_uint64 ("product_group_serial",
+ &pd->product_group_id),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_uint64 ("money_pot_serial",
+ &pd->money_pot_id),
+ NULL),
+ GNUNET_PQ_result_spec_bool ("price_is_net",
+ &pd->price_is_net),
GNUNET_PQ_result_spec_end
};
enum GNUNET_DB_QueryStatus qs;
check_connection (pg);
+ pd->product_group_id = 0;
+ pd->money_pot_id = 0;
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"lookup_product",
params,
diff --git a/src/backenddb/pg_update_product.c b/src/backenddb/pg_update_product.c
@@ -39,7 +39,9 @@ TMH_PG_update_product (void *cls,
bool *no_product,
bool *lost_reduced,
bool *sold_reduced,
- bool *stocked_reduced)
+ bool *stocked_reduced,
+ bool *no_group,
+ bool *no_pot)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
@@ -68,6 +70,13 @@ TMH_PG_update_product (void *cls,
cats,
pg->conn),
GNUNET_PQ_query_param_string (pd->product_name),
+ (0 == pd->product_group_id)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_uint64 (&pd->product_group_id),
+ (0 == pd->money_pot_id)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_uint64 (&pd->money_pot_id),
+ GNUNET_PQ_query_param_bool (pd->price_is_net),
GNUNET_PQ_query_param_end
};
uint64_t ncat;
@@ -87,6 +96,10 @@ TMH_PG_update_product (void *cls,
GNUNET_PQ_result_spec_uint64 ("no_cat",
&ncat),
&cats_found),
+ GNUNET_PQ_result_spec_bool ("no_group",
+ no_group),
+ GNUNET_PQ_result_spec_bool ("no_pot",
+ no_pot),
GNUNET_PQ_result_spec_end
};
enum GNUNET_DB_QueryStatus qs;
@@ -108,9 +121,12 @@ TMH_PG_update_product (void *cls,
",out_no_product AS no_product"
",out_no_cat AS no_cat"
",out_no_instance AS no_instance"
+ ",out_no_group AS no_group"
+ ",out_no_pot AS no_pot"
" FROM merchant_do_update_product"
"($1,$2,$3,$4::TEXT::JSONB,$5,$6,$7::TEXT::JSONB,$8,$9"
- ",$10,$11,$12,$13,$14,$15::TEXT::JSONB,$16,$17,$18,$19);");
+ ",$10,$11,$12,$13,$14,$15::TEXT::JSONB,$16,$17,$18"
+ ",$19,$20,$21,$22);");
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"update_product",
params,
diff --git a/src/backenddb/pg_update_product.h b/src/backenddb/pg_update_product.h
@@ -48,6 +48,8 @@
* exceed total_stock minus the existing total_sold;
* total_sold and total_stock must be larger or equal to
* the existing value;
+ * @param[out] no_group set to true if the product group in @a pd is unknown
+ * @param[out] no_pot set to true if the money pot in @a pd is unknown
* @return database result code
*/
enum GNUNET_DB_QueryStatus
@@ -62,6 +64,8 @@ TMH_PG_update_product (void *cls,
bool *no_product,
bool *lost_reduced,
bool *sold_reduced,
- bool *stocked_reduced);
+ bool *stocked_reduced,
+ bool *no_group,
+ bool *no_pot);
#endif
diff --git a/src/backenddb/pg_update_product.sql b/src/backenddb/pg_update_product.sql
@@ -36,12 +36,17 @@ CREATE FUNCTION merchant_do_update_product (
IN in_minimum_age INT4,
IN ina_categories INT8[],
IN in_product_name TEXT,
+ IN in_product_group_id INT8, -- NULL for default
+ IN in_money_pot_id INT8, -- NULL for none
+ IN in_price_is_net BOOL,
OUT out_no_instance BOOL,
OUT out_no_product BOOL,
OUT out_lost_reduced BOOL,
OUT out_sold_reduced BOOL,
OUT out_stocked_reduced BOOL,
- OUT out_no_cat INT8)
+ OUT out_no_cat INT8,
+ OUT out_no_group BOOL,
+ OUT out_no_pot BOOL)
LANGUAGE plpgsql
AS $$
DECLARE
@@ -54,6 +59,8 @@ DECLARE
my_price_array taler_amount_currency[];
BEGIN
+out_no_group = FALSE;
+out_no_pot = FALSE;
out_no_instance=FALSE;
out_no_product=FALSE;
out_lost_reduced=FALSE;
@@ -73,6 +80,30 @@ THEN
RETURN;
END IF;
+IF in_product_group_id IS NOT NULL
+THEN
+ PERFORM FROM merchant_product_groups
+ WHERE product_group_serial=in_product_group_id
+ AND merchant_serial=my_merchant_id;
+ IF NOT FOUND
+ THEN
+ out_no_group=TRUE;
+ RETURN;
+ END IF;
+END IF;
+
+IF in_money_pot_id IS NOT NULL
+THEN
+ PERFORM FROM merchant_money_pots
+ WHERE money_pot_serial=in_money_pot_id
+ AND merchant_serial=my_merchant_id;
+ IF NOT FOUND
+ THEN
+ out_no_pot=TRUE;
+ RETURN;
+ END IF;
+END IF;
+
-- Check existing entry satisfies constraints
SELECT total_stock
,total_stock_frac
@@ -168,6 +199,9 @@ UPDATE merchant_inventory SET
,address=in_address
,next_restock=in_next_restock
,minimum_age=in_minimum_age
+ ,product_group_serial=in_product_group_id
+ ,money_pot_serial=in_money_pot_id
+ ,price_is_net=in_price_is_net
WHERE merchant_serial=my_merchant_id
AND product_serial=my_product_serial; -- could also match on product_id
diff --git a/src/backenddb/test_merchantdb.c b/src/backenddb/test_merchantdb.c
@@ -44,28 +44,28 @@ static struct TALER_MERCHANTDB_Plugin *plugin;
* @param test 0 on success, non-zero on failure
*/
#define TEST_WITH_FAIL_CLAUSE(test, on_fail) \
- if ((test)) \
- { \
- GNUNET_break (0); \
- on_fail \
- }
+ if ((test)) \
+ { \
+ GNUNET_break (0); \
+ on_fail \
+ }
#define TEST_COND_RET_ON_FAIL(cond, msg) \
- if (! (cond)) \
- { \
- GNUNET_break (0); \
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR, \
- msg); \
- return 1; \
- }
+ if (! (cond)) \
+ { \
+ GNUNET_break (0); \
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, \
+ msg); \
+ return 1; \
+ }
/**
* @param __test 0 on success, non-zero on failure
*/
#define TEST_RET_ON_FAIL(__test) \
- TEST_WITH_FAIL_CLAUSE (__test, \
- return 1; \
- )
+ TEST_WITH_FAIL_CLAUSE (__test, \
+ return 1; \
+ )
/* ********** Instances ********** */
@@ -824,6 +824,8 @@ test_insert_product (const struct InstanceData *instance,
bool conflict;
bool no_instance;
ssize_t no_cat;
+ bool no_group;
+ bool no_pot;
TEST_COND_RET_ON_FAIL (expected_result ==
plugin->insert_product (plugin->cls,
@@ -834,7 +836,9 @@ test_insert_product (const struct InstanceData *instance,
cats,
&no_instance,
&conflict,
- &no_cat),
+ &no_cat,
+ &no_group,
+ &no_pot),
"Insert product failed\n");
if (expected_result > 0)
{
@@ -877,6 +881,8 @@ test_update_product (const struct InstanceData *instance,
bool lost_reduced;
bool sold_reduced;
bool stocked_reduced;
+ bool no_group;
+ bool no_pot;
TEST_COND_RET_ON_FAIL (
expected_result ==
@@ -891,7 +897,9 @@ test_update_product (const struct InstanceData *instance,
&no_product,
&lost_reduced,
&sold_reduced,
- &stocked_reduced),
+ &stocked_reduced,
+ &no_group,
+ &no_pot),
"Update product failed\n");
if (expected_result > 0)
{
diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h
@@ -431,6 +431,23 @@ struct TALER_MERCHANTDB_ProductDetails
* restrictions.
*/
uint32_t minimum_age;
+
+ /**
+ * Group in which the product is in. 0 for default group.
+ */
+ uint64_t product_group_id;
+
+ /**
+ * Money pot into which sales of this product should go into by default.
+ */
+ uint64_t money_pot_id;
+
+ /**
+ * True if the price for this product is given in net,
+ * False if its the gross price.
+ */
+ bool price_is_net;
+
};
@@ -2404,6 +2421,7 @@ struct TALER_MERCHANTDB_Plugin
struct TALER_MERCHANTDB_ProductDetails *pd,
size_t *num_categories,
uint64_t **categories);
+
/**
* Lookup product image by its hash.
*
@@ -2447,6 +2465,8 @@ struct TALER_MERCHANTDB_Plugin
* @param[out] conflict set to true if a conflicting
* product already exists in the database
* @param[out] no_cat set to index of non-existing category from @a cats, or -1 if all @a cats were found
+ * @param[out] no_group set to true if the product group in @a pd is unknown
+ * @param[out] no_pot set to true if the money pot in @a pd is unknown
* @return database result code
*/
enum GNUNET_DB_QueryStatus
@@ -2458,7 +2478,10 @@ struct TALER_MERCHANTDB_Plugin
const uint64_t *cats,
bool *no_instance,
bool *conflict,
- ssize_t *no_cat);
+ ssize_t *no_cat,
+ bool *no_group,
+ bool *no_pot);
+
/**
* Update details about a particular product. Note that the
@@ -2479,6 +2502,8 @@ struct TALER_MERCHANTDB_Plugin
* @param[out] lost_reduced the update failed as the counter of units lost would have been lowered
* @param[out] sold_reduced the update failed as the counter of units sold would have been lowered
* @param[out] stocked_reduced the update failed as the counter of units stocked would have been lowered
+ * @param[out] no_group set to true if the product group in @a pd is unknown
+ * @param[out] no_pot set to true if the money pot in @a pd is unknown
* @return database result code
*/
enum GNUNET_DB_QueryStatus
@@ -2493,7 +2518,10 @@ struct TALER_MERCHANTDB_Plugin
bool *no_product,
bool *lost_reduced,
bool *sold_reduced,
- bool *stocked_reduced);
+ bool *stocked_reduced,
+ bool *no_group,
+ bool *no_pot);
+
/**
* Lock stocks of a particular product. Note that the transaction must