Here, the different problems about unit conversion are explained.
Table of Contents
The problems related to Unit Conversion¶
Example¶
Let's suppose we have the following products:
- 'Shortbreads (*8)':
- ref: 10008
- default unit: unit (1 box)
- contents: 8 biscuits
- net weight: 150g
- gross weight: 162g
- volume: ~100cm³
- 'Shortbreads (*12)':
- ref: 10012
- contents: 12 biscuits
- net weight: 225g
- ...
- 'SomePack':
- ref: 88888
- contents: 2 'Shortbreads (*8)', 1 'Shortbreads (*12)', 1 'SomeSoda'
- ...
Here are questions we want to answer:
- 1. How many 10008 do we have in stock? (in the default managment unit)
- 2. How many kg (gross weight) of 10008 do we have? Same question for net weight.
- How is the occupation of our stock?
- 3. How many 'Shortbreads (*8)' including packs?
- 4. How many shortbreads?
- 5. What is the (gross) weight of our stock of 10008, 10012 and 88888?
This example can be more complicated: What if there are several flavours of shortbreads and we want to answer the above questions, depending on whether we ignore or not flavours.
Problem 1 (solved)¶
'biscuits', 'net weight', 'gross weight', 'box', 'volume': what are they?
Problem 2 (solved)¶
The quantity unit on resources should allow multiple categories (as it is for apparel) so that a given product can be sold using its default unit or any other acceptable unit.
Problem 3: linear? (solved)¶
For a given product, there is always a constant ratio between 2 of its properties. And for any product containing shortbreads, the ratio between the number of biscuits and the net
weight is also the same.
However, we lose linearity for certain properties if we consider, for example, a box of 12 and another of 8: for these 2 products, the ratio between net weight and gross weight
varies.
Problem 4: packs (solved)¶
How to describe packs? Should we build a graph of products? Should we settle for computing sums of the subproducts properties manually?
Problem 5 (solved)¶
We want to compute totals in only one SQL request. What information do we need in the SQL database? When and where do we have to perform unit conversion?
Problem 6 (solved)¶
A 'Shortbread' resource may already exist. This is the case if we produce them. Why not seeing 'Shortbreads (*8)' as a pack of 8 'Shortbread'?
Problem 7 (solved)¶
Let's suppose we have a 'Shortbread' product. We could get the (net) weight of shortbreads in our stock of 'Shortbreads (*8)' and 'Shortbreads (*12)' by getting the number of
'Shortbread' first and then look up the properties of the 'Shortbread' to perform conversion.
However, question 5 prevents us to do that. Let's rephrase it in a more generic way: « How to count several unrelated resources in a given common property? » If there is no
common resource, we are forced to keep in SQL all conversion factors of all products.
Problem 8: variation (solved)¶
The model must handle variants of products, even when they are optional.
Problem 9: price¶
Should be also handle price like other properties?
ex: What if we want to buy stuff for a certain amount of euros?
Draft¶
All unit conversions is done in Python:
- before our SQL request, so that all computation is done in the same unit, whatever the product.
- after our SQL request, to display the result in the unit requested by the user.
We don't do (and we shouldn't) unit conversion between 2 different physical quantities.
This model handles non-optional and optional variations.
quantity_unit (base category)¶
This base category has a clear usage: it is only a database of measurement units. It means that it is filled independantly from any product. In a first time, it would include
units with a SI prefix.
Units used for a given physical quantity are grouped in the same subcategory.
=> quantity_unit//
Each unit has a quantity field containing the ratio in respect to a base unit (of the same physical quantity). The ratio is 1 for a base one.
- quantity_unit/unit/unit (quantity = 1)
- quantity_unit/unit/dozen (quantity = 12)
- quantity_unit/mass/pound (quantity = .45359237)
- quantity_unit/mass/g (quantity = .001)
- quantity_unit/mass/kg (quantity = 1)
metric_type (base category)¶
This new base category defines any « property » a product might have. It allows to quantify a product in different metrics, even if the same physical unit is used for several
metrics.
The schema must be similar to quantity_unit so that it is possible to determine relevant units for each metric type.
=> metric_type//
- metric_type/mass/Net_weight
- metric_type/mass/Gross_weight
- metric_type/volume/Volume
- metric_type/unit/item/food/Shortbread
NOTE: There is probably no point in distinguishing 'Gross weight' from generic 'Mass' (or 'Weight').
remaining questions¶
metric_type/volume/Volume, metric_type/unit/Unit (and so on) could be simplified respectively to metric_type/volume, metric_type/unit, etc.
- Are node categories valid metric types?
- Are « first level » categories (ex: metric_type/volume) valid metric types?
Products¶
Each product has a list of measures, including a default one. A measure consists of a metric_type, a quantity_unit and a value (quantity).
'Shortbreads (*8)' (default unit in bold)
metric_type/unit/unit |
quantity_unit/unit/unit |
1 |
metric_type/unit/item/food/Shortbread |
quantity_unit/unit/unit |
8 |
metric_type/mass/Net_weight |
quantity_unit/mass/g |
150 |
metric_type/mass/Gross_weight |
quantity_unit/mass/g |
162 |
metric_type/volume/Volume |
quantity_unit/volume/cm3 |
100 |
It seems the first line would be almost always there. Should it be implicit?
measure (measurement ?)¶
A new measure table with 4 columns allows computing totals, for one or several products, and for one property that is common to these products. Schema:
create table measure (
product_uid int,
variation varchar(255),
metric_type_uid int,
primary key (product_uid, variation, metric_type_uid),
value float
);
The variation column is compared with the variation_text column of the stock table and they match if the former is a subset of the latter: both columns are strings representing
sets of variation categories. For the stock table, it's still a list of category paths separated by '\n'. In the new table, the variation column is a regular expression.
So the condition is: stock.variation_text REGEXP measure.variation
REGEXP are used instead of LIKE because of optional varations. Another solution is to change the format of stock.variation_text. Given a foo/bar option, the string foo/ would be
added to variation_text if the option is missing, so that foo/_% matches the presence of a foo option, foo/ matches the absence and foo/% matches all cases.
The unit of the 4th column (value) must be a base one.
example¶¶¶¶ of table
('Shortbreads (*8)') |
'' |
(metric_type/unit/unit) |
1 |
('Shortbreads (*8)') |
'' |
(metric_type/item/food/Shortbread) |
8 |
('Shortbreads (*8)') |
'' |
(metric_type/volume/Volume) |
.0001 |
example¶¶¶¶ of variation regex
Given a product with 3 variation axes, logo/erp5 being optional:
- ^colour/[^\n]+\n(logo/erp5\n)?size/small$ matches any 'size/small' variant
- ^colour/[^\n]+\nsize/[^\n]+$ matches variants without logo
pack¶
Another pack table allows conversion from a number of certain products to a number of other products: the table records the contents of each composed product. It allows computing
totals for packs according to a property of one of its component(s).
create table pack (
product_uid int,
product_variation varchar(255),
component_uid int,
component_variation varchar(255),
primary key (product_uid, product_variation, component_uid, component_variation),
value float
);
The product_variation column is like sub_quant's variation column.
The component_variation column is like stock's variation_text column.
naming proposal¶
- pack¶ | bundle | assortment
- composed_product
The following table contain a fourth row in case there exists a 'Shortbread' product.
('SomePack') |
'' |
('Shortbreads (*8)') |
'' |
2 |
('SomePack') |
'' |
('Shortbreads (*12)') |
'' |
1 |
('SomePack') |
'' |
('SomeSoda') |
'' |
1 |
('SomePack') |
'' |
('Shortbread') |
'' |
28 |
Implementation¶
getInventoryList('Shortbreads (*8)'):
SELECT
...
FROM
stock LEFT JOIN catalog AS section ON (section.uid =
stock.section_uid),
catalog AS catalog, catalog as node, catalog as resource
WHERE
(stock.resource_uid = '1499') AND (catalog.uid = stock.uid)
AND node.uid = stock.node_uid
AND resource.uid = stock.resource_uid
GROUP BY
stock.node_uid, stock.uid, stock.resource_uid
A simple case without pack¶
If we want the net weight, the request becomes:
SELECT
SUM(stock.quantity * measure.value)
...
FROM
stock LEFT JOIN catalog AS section ON (section.uid =
stock.section_uid),
catalog AS catalog, catalog as node, catalog as resource
, measure
WHERE
stock.resource_uid IN (/* list of product uid we want to take into
account */)
/* AND, optionally, a condition on stock.variation_text */
AND catalog.uid = stock.uid
AND node.uid = stock.node_uid
AND resource.uid = stock.resource_uid
AND resource.uid = measure.product_uid
AND stock.variation_text REGEXP measure.variation
AND measure.metric_type_uid IN (SELECT uid FROM catalog
WHERE portal_type = 'Category' AND relative_url =
'metric_type/mass/Net_weight')
GROUP BY
stock.node_uid /*, stock.uid */ /*, stock.resource_uid */
With packs¶
If we have we have a 'Shortbread' product with an associated mass, we can get the mass of shortbreads in the stock of 'SomePack':
SELECT
SUM(stock.quantity * pack.value * measure.value)
...
FROM
stock LEFT JOIN catalog AS section ON (section.uid =
stock.section_uid),
catalog AS catalog, catalog as node, catalog as resource
, measure, pack
WHERE
stock.resource_uid IN (/* 'SomePack' uid */)
/* AND, optionally, a condition on stock.variation_text */
AND catalog.uid = stock.uid
AND node.uid = stock.node_uid
AND resource.uid = stock.resource_uid
AND resource.uid = pack.product_uid
AND stock.variation_text REGEXP pack.product_variation
AND pack.component_uid = /* 'Shortbread' uid */
/* AND, optionally, a condition on pack.component_variation */
AND pack.component_uid = measure.product_uid
AND pack.component_variation REGEXP measure.variation
AND measure.metric_type_uid IN (SELECT uid FROM catalog
WHERE portal_type = 'Category' AND relative_url =
'metric_type/mass/Weight')
GROUP BY
stock.node_uid /*, stock.uid */ /*, stock.resource_uid */
Related Articles¶