Most Powerful Open Source ERP

Unit Conversion

Here, the different problems about unit conversion are explained.
  • Last Update:2016-05-16
  • Version:001
  • Language:en

Here, the different problems about unit conversion are explained.

Table of Contents

  • The problems related to Unit Conversion
  • Draft
  • Implementation
  • Related Articles

    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.

    example

    • 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//

    example

    • 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).

    example

    '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?

    SQL

    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

    example

    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

    SQL

    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