Home SuiteQL Recipe: Work Order Variance
Post
Cancel

SuiteQL Recipe: Work Order Variance

Introduction

This post is the first in a series of posts that will cover a specific use case for SuiteQL. If you are not familiar with SuiteQL, I recommend reading the SuiteQL Introduction series first.

The following post assumes you have advanced bills of materials set up in NetSuite and are not using wip for your work orders.

Business Use Case

The business use case is as follows. When creating a work order, NetSuite will populate the work order with the default bill of materials for the assembly. See the Implementation: Advanced Bill of Materials post for more information on how to set up advanced bills of materials in NetSuite.

Whether it was populated by the default BOM or by the user selecting a BOM, the work order will then populate the components of the BOM into the work order. The user can add and remove components from the work order, and also change the quantity of the components.

When the work order is transformed into an assembly build, the user can then edit the quantity once more on the assembly build transaction (adding or removing components is not allowed). The business use case is to calculate the variance between the work order, assembly build, and the bill of materials. We’ll assume that you only have the work order id (1234) and want to calculate the variance.

Getting the Components

Getting the components is not as simple as you might think. We can query the work order, assembly build, or BOM for the components, but because the user can add and remove components to the work order, if we choose either of them we might be missing components. As such, we will use a UNION query to get all of the components from the BOM and the work order. We don’t need to worry about the assembly build because adding and removing components is not supported.

1
2
3
4
5
6
7
8
9
10
11
12
13
WITH allitems AS (
  SELECT
    WorkorderLines.item AS item
    FROM TransactionLine AS WorkorderLines
    WHERE WorkorderLines.transaction = 1234
    and WorkorderLines.mainline = 'F'
  UNION
  SELECT
    BomRevisionComponentMember.item AS item,
    FROM BomRevisionComponentMember
    INNER JOIN Transaction ON Transaction.id = 1234
    AND Transaction.billofmaterialsrevision = BomRevisionComponentMember.bomrevision
)

The UNION will ensure that we get a distinct list of all the components from the BOM and the work order.

Getting the Quantities

For the purposes of readability I’m going to split this into three CTEs, for the work order, BOM revision, and for the assembly build. Because transaction lines have a negative quantity for the work order and the assembly build, we’ll multiply the quantity by -1 to get the positive quantity.

First the Work Order (we use mainline = 'F' to exclude the assembly item):

1
2
3
4
5
6
7
8
WITH WorkorderLines AS (
  SELECT
    WorkorderLines.item AS item,
    WorkorderLines.quantity * -1 AS quantity
    FROM TransactionLine AS WorkorderLines
    WHERE WorkorderLines.transaction = 1234
    and WorkorderLines.mainline = 'F'
)

This will give us the quantity in the base unit of measure of the item.

Now the assembly build:

1
2
3
4
5
6
7
8
9
WITH AssemblyBuildLines AS (
  SELECT
    AssemblyBuildLines.item AS item,
    AssemblyBuildLines.quantity * -1 AS quantity
    FROM TransactionLine AS AssemblyBuildLines
    WHERE AssemblyBuildLines.createdFROM = 1234
    and AssemblyBuildLines.mainline = 'F'
)

This again will give us the quantity in the base unit of measure of the item.

For the BOM revision, it’s a bit more involved. We need to multiply the quantity on the BOM revision by the quantity for the work order. We can get the quantity by joining the main line of the work order to the BOM revision.

1
2
3
4
5
6
7
8
9
WITH BomRevisionLines as (
  SELECT
    BomRevisionComponentMember.item as item,
    BomRevisionComponentMember.quantity * transactionline.quantity as quantity
    FROM BomRevisionComponentMember
    INNER JOIN Transaction on Transaction.id = 1234
    AND Transaction.billofmaterialsrevision = BomRevisionComponentMember.bomrevision
    INNER JOIN transactionline on transactionline.transaction = transaction.id and transactionline.mainline = 'T'
)

This will give us the quantity in the units of measure specified in the BOM revision.

To make it simpler to calculate the variance, we will convert the BOM revision quantities to the base unit of measure. See SuiteQL: Units Of Measure for more information.

1
2
3
4
5
6
7
8
9
10
WITH BomRevisionLines AS (
  SELECT
    BomRevisionComponentMember.item AS item,
    BomRevisionComponentMember.quantity * TransactionLine.quantity * UnitsTypeUom.conversionrate AS quantity
    FROM BomRevisionComponentMember
    INNER JOIN Transaction ON Transaction.id = 1234
    AND Transaction.billofmaterialsrevision = BomRevisionComponentMember.bomrevision
    INNER JOIN UnitsTypeUom ON UnitsTypeUom.internalid = BomRevisionComponentMember.units
    INNER JOIN TransactionLine ON TransactionLine.transaction = transaction.id and TransactionLine.mainline = 'T'
)

Now we have the bom quantities in the base unit of measure.

Calculating the Variance

Assuming we all CTEs loaded into the query, we can now calculate the variance. (I omitted the CTEs for brevity; the full query is below.)

1
2
3
4
5
6
7
8
9
10
(CTEs)
SELECT
  AllItems.item as item,
  WorkOrderLines.quantity as workorderquantity,
  AssemblyBuildLines.quantity as assemblybuildquantity,
  BomRevisionLines.quantity as bomquantity,
  FROM AllItems
  LEFT JOIN WorkOrderLines on WorkOrderLines.item = allitems.item
  LEFT JOIN AssemblyBuildLines on AssemblyBuildLines.item = allitems.item
  LEFT JOIN BomRevisionLines on BomRevisionLines.item = allitems.item

Now the report will look something like this:

ItemWork Order QuantityAssembly Build QuantityBOM Quantity
111 (Internal ID)131410
122 (Internal ID)202025
133 (Internal ID)11100

As we can see the report is pretty bare.

The column Item is the internal id, and the quantities are in the base unit of measure.

Let’s spice things up.

Spicing Things Up

Let’s get the item name and convert the quantities to the consumption unit of measure on the item record. We’ll also replace the columns Work Order Quantity and Assembly Build Quantity to be a variance column. We’ll add a column with assembly cost (assuming average cost).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(CTEs)
SELECT
  item.itemid as itemid,
  (bomrevisionlines.quantity)/ConsumptionUom.conversionrate as bomquantity,
  (bomrevisionlines.quantity - workorderlines.quantity)/ConsumptionUom.conversionrate as workordervariance,
  (bomrevisionlines.quantity - assemblylines.quantity)/ConsumptionUom.conversionrate as assemblyvariance,
  ConsumptionUom.pluralAbbreviation as consumptionunit,
  (assemblylines.quantity * item.averagecost)/ConsumptionUom.conversionrate as assemblycost
  FROM allitems
  LEFT JOIN workorderlines on workorderlines.item = allitems.item
  LEFT JOIN assemblybuildlines on assemblybuildlines.item = allitems.item
  LEFT JOIN bomrevisionlines on bomrevisionlines.item = allitems.item
  INNER JOIN Item on Item.id = allitems.item
  INNER JOIN UnitsTypeUom as ConsumptionUom on ConsumptionUom.internalid = Item.consumptionunit

This will give us a report that looks like this:

ItemBOM QuantityWork Order VarianceAssembly VarianceConsumption UnitAssembly Cost
Component 11034Bag (50 LB)$10
Component 225-5-5KG$25
Component 301110LB$12

We’ll have to wrap some columns in COALESCE to make sure we don’t get any null values.

Here is the full query

Full Query
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
WITH allitems AS (
  SELECT
    WorkorderLines.item AS item
    FROM TransactionLine AS WorkorderLines
    WHERE WorkorderLines.transaction = 1234
    and WorkorderLines.mainline = 'F'
  UNION
  SELECT
    BomRevisionComponentMember.item AS item,
    FROM BomRevisionComponentMember
    INNER JOIN Transaction ON Transaction.id = 1234
    AND Transaction.billofmaterialsrevision = BomRevisionComponentMember.bomrevision
), WorkorderLines AS (
  SELECT
    WorkorderLines.item AS item,
    WorkorderLines.quantity * -1 AS quantity
    FROM TransactionLine AS WorkorderLines
    WHERE WorkorderLines.transaction = 1234
    and WorkorderLines.mainline = 'F'
), AssemblyBuildLines AS (
  SELECT
    AssemblyBuildLines.item AS item,
    AssemblyBuildLines.quantity * -1 AS quantity
    FROM TransactionLine AS AssemblyBuildLines
    WHERE AssemblyBuildLines.createdFROM = 1234
    and AssemblyBuildLines.mainline = 'F'
), BomRevisionLines AS (
  SELECT
    BomRevisionComponentMember.item AS item,
    BomRevisionComponentMember.quantity * TransactionLine.quantity * UnitsTypeUom.conversionrate AS quantity
    FROM BomRevisionComponentMember
    INNER JOIN Transaction ON Transaction.id = 1234
    AND Transaction.billofmaterialsrevision = BomRevisionComponentMember.bomrevision
    INNER JOIN UnitsTypeUom ON UnitsTypeUom.internalid = BomRevisionComponentMember.units
    INNER JOIN TransactionLine ON TransactionLine.transaction = transaction.id and TransactionLine.mainline = 'T'
)
SELECT
  item.itemid AS itemid,
  (COALESCE(BomRevisionLines.quantity, 0))/ConsumptionUom.conversionrate AS bomquantity,
  (COALESCE(BomRevisionLines.quantity, 0) - COALESCE(WorkorderLines.quantity, 0))/ConsumptionUom.conversionrate AS workordervariance,
  (COALESCE(BomRevisionLines.quantity, 0) - COALESCE(AssemblyBuildLines.quantity, 0))/ConsumptionUom.conversionrate AS assemblyvariance,
  ConsumptionUom.pluralAbbreviation AS consumptionunit,
  (COALESCE(AssemblyBuildLines.quantity, 0) * item.averagecost)/ConsumptionUom.conversionrate AS assemblycost
  FROM allitems
  INNER JOIN Item ON Item.id = allitems.item
  INNER JOIN UnitsTypeUom AS ConsumptionUom ON ConsumptionUom.internalid = Item.consumptionunit
  LEFT JOIN WorkorderLines ON workorderlines.item = allitems.item
  LEFT JOIN AssemblyBuildLines ON AssemblyBuildLines.item = allitems.item
  LEFT JOIN BomRevisionLines ON bomrevisionlines.item = allitems.item
This post is licensed under CC BY 4.0 by the author.

Implementation: Advanced Bill of Materials

Anatomy of SuiteScript: Introduction