Units of Measurement in 1C Applications

Alexander Biryukov

22.07.2021 16 min

Pic.png

When designing IT systems, software architects often have to ensure a single item of goods can use several units of measurement.

What are possible scenarios here? Imagine we have a food packaging business dealing with grain. We buy goods from suppliers, put them in convenient packaging, and deliver packages to retail chains. Naturally, suppliers calculate weight in tons, while we want to deal with kilograms. No doubt, we need to decide which units of measurement to use. Should it be tons, kilograms, or something else?

Here I would like to offer one possible solution to the task.

Start with creating a 1C configuration. As we work with goods, we cannot go without a catalog. Let's create it.

image_01.png

At this stage "Products" catalog does not need any attributes.

As we have an entity called "Unit of Measurement," it is obvious we need a catalog to store it. Time to create the second catalog called "UOMClassifier."

image_02.png

This catalog keeps standard units of measurement, such as kilogram, ton, liter, unit, and others.

Our next step is to link "Products" and "UOMClassifier" catalogs. Here we need some accounting theory before we can proceed.

So we have an item of goods that can use several units of measurement, like tons and kilograms. It is evident that we cannot use both units at a time.

To be honest, we can actually record stock balance in various units, but this will make the software more complicated. And one of the primary architect's tasks is to make architecture as plain as possible, or in order words, "normalize" the data storage process. It means that nobody will use several units of measurement for a single item of goods in real life.

Thus, each item requires a single base unit of measurement. That is, we want to use this one unit of measurement to calculate the balance. In case we need to apply other units in sale or purchase transactions, the application automatically calculates the value through conversion ratio.

Add "MeasurementUnit" attribute, type "CatalogRef.UOMClassifier" to "Products" catalog:

image_03.png

The unit of measurement specified in the attribute is considered a base unit for balances (records in registers).

And this brings one important thing to our attention. If the application already contains balances (i.e., registers have records on the amount of the goods), users should be unable to change units of measurement.

Example: We have a 150-meter paper roll in our warehouse. Our employee opens the "Products" catalog and changes units of measurement from meters to kilometers. At that numerical value remains the same. As a result, the system report 150-kilometer (!) paper roll in stock. You can imagine such change will not make the company management happy.

So the golden rule is to prevent any changes to the item's base unit. An employee can assign a unit of measurement only when adding a new item of goods. After that, the unit of measurement cannot be changed. Please note that 1C platform does not perform such checks, and it is the developer's responsibility to add applicable code to the application.

Are we done yet? Not really. Let's take a moment and consider this. We want to use a classifier (catalog) for units of measurement, but these can be very different. Among them are kilograms, kilometers, miles, and pounds, to name a few. Add to it boxes and crates that units of measurement as well. Now imagine employees working on invoices having to scroll through this insane list of units. How happy and efficient will they be? What are the chances of quickly getting an error-free document?

You guessed it. Picking up units of measurement every time a new document is created will obviously slow down the work process and is likely to cause multiple errors. What can we do about it? One of the possible options is to introduce a third entity that can build a relationship between "Products" and "UOMClassifier" catalogs. There are several ways to implement it. Thus, we could utilize an information register for the task. But in our case, it is sufficient to use another catalog as the third entity.

Create "UOM" catalog and add "Ratio" attribute, with type "Number," and "UOMClassifier" attribute, with type "CatalogRef.UOMClassifier":

image_04.png

It is time for a very important thing: assigning an owner for the newly created catalog. The owner here should be "Products" catalog:

image_05.png

This makes it possible to establish a one-to-many relationship that means one item can have several units of measurement. And with the "Ratio" attribute, we are able to convert units.

We want to use the "UOM" catalog to store optional units of measurement, while the item base unit is defined directly in the "Products" catalog ("MeasurementUnit" attribute).

Let me create a very basic "SupplierInvoice" document to demonstrate the concept of units of measurement processing. Create the document within our configuration and add the "Inventory" tabular section to it. Now we need to add two attributes that are "Product" and "Quantity":

image_06.png

In case we choose "pieces" as a base unit (the unit to be used in the database), and a supplier delivers goods in boxes, 10 pieces each, the "SupplierInvoice" should state the number of boxes, not pieces.

At that, the balance register should record the quantity in pieces. To do it, we need one more attribute called "MeasurementUnit."

image_07.png

Its role is to store units of measurement for a particular type of document to make possible the use of proper units of measurement when posting the document.

Thus if our base unit of measurement is set to pieces (database uses this unit for balances), and "SupplierInvoice" specifies 10 pieces, the database records it as 10. But in case the document unit of measurement is set to "boxes" with a "Ratio" of 5 (i.e., 5 pieces per box) and declared quantity is 10, the database records it as 50 pieces (5 x 10).

This is how the "MeasurementUnit" attribute works. But what type should we assign to this attribute? As you remember, we have two catalogs: "UOMClassifier" as a unified classifier for units of measurement and "UOM" as a place to store units of measurement for each item of goods. Again. What type should we assign to the "MeasurementUnit" attribute?

The answer is that we need to use composite data type. This enables the "MeasurementUnit" attribute to store values of both catalogs: "UOMClassifier" and "UOM":

image_08.png

What is the benefit of such an approach? Imagine we have an item of goods calculated in kg. The attribute applied to such an item is the "UOMClassifier" catalog. In order to add the abovementioned item of goods into our document, the "MeasurementUnit" should be attributed to the respective catalog.

Next, in case we want to use an additional unit of measurement, say gram, we need the "UOM" catalog. If we wish to specify the amount of the goods in grams, "MeasurementUnit" should be attributed to the respective catalog type. Now it is getting obvious that the "MeasurementUnit" attribute for the "SupplierInvoice" document should be of the composite type.

And final touch. As the "UOM" catalog is subordinate to the "Products" catalog, when choosing the "UOM" catalog, users should be able to see only units of measurement relating to a particular item of goods. How do we arrange for it? One of the possible options is to create handlers for relevant events. Still, there is an easier way.

Use the "Choice parameters links" property in the "MeasurementUnit" attribute:

image_09.png

 

image_10.png

By defining the "Choice parameters links" property, we establish a connection between goods and units of measurement. Now users can see only units of measurement relating to a particular item of goods.

Basically, this is all that has to do with the solution architecture, and we are almost ready to proceed with coding.

Start the application and fill in the catalogs. Run configuration, go to the "UOMClassifier" catalog, and fill it in:

image_11.png

Select the "Products" catalog and add a single item of goods. Choose kilogram as a base unit of measurement:

image_12.png

Add gram and ton as extra units of measurement for this item of goods.

image_13.png

Make sure "Ratio" and "UOMClassifier" values are correct.

Go back to Designer and create the "SupplierInvoice" form:

image_14.png

Now we need to write a code that can pick data for the "MeasurementUnit" attribute with the account of an item of goods selected. For this purpose you can use the "OnChange" event for the "Product" attribute in the tabular section of the document:

image_15.png

 

The event can be processed with the following code:

&AtClient

Procedure InventoryProductOnChange(Item)

           

       TabularSectionRow = Items.Inventory.CurrentData;

           

       TabularSectionRow.MeasurementUnit = ReturnDefaultMeasurementUnit(TabularSectionRow.Product);

           

EndProcedure

&AtServer

Function ReturnDefaultMeasurementUnit(Product)

           

        Return Product.MeasurementUnit;

           

EndFunction

The idea is simple. When an item of goods in the document's tabular section is changed, the "MeasurementUnit" attribute gets a default unit of measurement for a particular item.

Time to check how it works. Start 1C in dialog mode, create a new "SupplierInvoice" document, and add an item of goods to the document. The application automatically adds a unit of measurement:

image_16.png

Great! We are halfway through. If we try to select another unit of measurement, the application requests data type and only then show the list of available units:

image_17.png

errors. So our next step is to create a code to allow users to select units of measurement with comfort.

Go back to Designer and create a handler in the "SupplierInvoice" form for the "OnChange" event in the document tabular section:

image_18.png

Here is the code.

&AtClient

Procedure InventoryOnChange(Item)

           

            TabularSectionRow = Items.Inventory.CurrentData;

           

            If Not TrimAll(TabularSectionRow.Product) = "" Then

                       listChoiceList = ReturnChoiceList(TabularSectionRow.Product);

                       Item.ChildItems.InventoryMeasurementUnit.ChoiceList.LoadValues(listChoiceList);

            EndIf;

           

EndProcedure

&AtServer

Function ReturnChoiceList(Product)

           

            arrayChoiceList = New Array;

           

            arrayChoiceList.Add(Product.MeasurementUnit);

           

            query = New Query;

            query.Text = "SELECT

               |             UOM.Ref AS Ref

               |FROM

               |             Catalog.UOM AS UOM

               |WHERE

               |             UOM.Owner = &Owner";

           

            query.SetParameter("Owner", Product);

           

            selection = query.Execute().Select();

           

            While selection.Next() Do

                       arrayChoiceList.Add(selection.Ref);

            EndDo;

           

            Return arrayChoiceList;

           

EndFunction

Time to check if the code runs properly. Start 1C, create a new "SupplierInvoice" document, add an item of goods and try picking a unit of measurement:

 

image_19.png

You automatically get a list of applicable units of measurement and can pick the one you need. This is how the final document might look like:

image_20.png

In means, the supplier has provided 1500 grams of goods. But we have agreed that a kilogram is a base unit of measurement. Thus, the database (balance register) should contain 1.5. There is no need to add a kilogram as a unit of measurement as it is linked to the item of goods and remains unchanged throughout the lifespan of the goods.

And now we are getting to the most exciting part of this article: writing document data in the register.

Let us create "Inventory" accumulation register. This is a place for our balances. Consequently, the register type is "Balances":

image_21.png

Now create "Products" dimension with "CatalogRef.Products" type:

image_22.png

and "Quantity" resource that we need to store data on the quantity of the goods:

image_23.png

We also need to include a recorder in our register. By recorders we mean documents that initiate records in a register.

image_24.png

Now it is time to create a code for the "SupplierInvoice" document. This code should write data into the register we have just created. Open our document in Designer, go to the "Records" tab and run "Record wizard":

image_25.png

You get the wizard form with register record type already set to "Receipt." Select the tabular section:

image_26.png

After that, you can click the "Fill Expressions" button or double click all required attributes in the "Document attributes" to the right to fill the lower form in:

image_27.png

Click "OK." The wizard creates the code and opens it for review. It should look something like this:


Procedure Posting(Cancel, Mode)

            //{{__REGISTER_REGISTERRECORDS_WIZARD

            // This fragment was built by the wizard.

            // Warning! All manually made changes will be lost next time you use the wizard.

            // register Inventory Receipt

            RegisterRecords.Inventory.Write = True;

            For Each CurRowInventory In Inventory Do

                              Record = RegisterRecords.Inventory.Add();

                              Record.RecordType = AccumulationRecordType.Receipt;

                              Record.Period = Date;

                              Record.Products = CurRowInventory.Product;

                              Record.Quantity = CurRowInventory.Quantity;

            EndDo;

            //}}__REGISTER_REGISTERRECORDS_WIZARD

EndProcedure


Let's make some adjustments. Make sure you have the "Include in the command interface" flag selected for the "Inventory" register:

image_28.png

Go to "DocumentForm" and set the "Inventory" register to visible state in the "Command interface" section.

image_29.png

This flag makes visible all document register records right in the form.

Start 1C in dialog mode again, and post the previously created "SupplierInvoice" document. Switch to the "Inventory" tab:

image_30.png

 

 

image_31.png

As you can see, the code created by the wizard has worked fine, and the "Inventory" register received a record of the amount of the goods.

Still, it states 1,500 instead of 1.5, while the application had to convert 1,500 grams to 1.5 kilograms as kilogram is the base unit of measurement here.

To make it look good, we need a small addition that converts the amount of the goods during document posting. Let me remind you once again. All amounts should be stated in kilograms. So get to the "SupplierInvoice" module and change the code previously created by the wizard as follows:

Procedure Posting(Cancel, Mode)

         //{{__REGISTER_REGISTERRECORDS_WIZARD

         // This fragment was built by the wizard.

         // Warning! All manually made changes will be lost next time you use the wizard.

         // register Inventory Receipt

         RegisterRecords.Inventory.Write = True;

           

         For Each CurRowInventory In Inventory Do

                   Record = RegisterRecords.Inventory.Add();

                   Record.RecordType = AccumulationRecordType.Receipt;

                   Record.Period = Date;

                   Record.Products = CurRowInventory.Product;

                   //Record.Quantity = CurRowInventory.Quantity;

                             

                   structureParameters = New Structure;

                   structureParameters.Insert("MeasurementUnit", CurRowInventory.MeasurementUnit);

                   structureParameters.Insert("Quantity", CurRowInventory.Quantity);

                             

                   Record.Quantity = ReturnCurrentQuantity(structureParameters);

                             

           EndDo;

           //}}__REGISTER_REGISTERRECORDS_WIZARD

EndProcedure

Function ReturnCurrentQuantity(structureParameters)

           

            currentMeasurementUnit = structureParameters.MeasurementUnit;

            currentQuantity                 = structureParameters.Quantity;

           

            If TypeOf(currentMeasurementUnit) = Type("CatalogRef.UOMClassifier") Then

                       Return currentQuantity;

            Else

                       Return currentQuantity * currentMeasurementUnit.Ratio;

            EndIf;

           

EndFunction

As you can see the changes are not that big. The application checks the current "MeasurementUnit" on posting. If it has the "CatalogRef.UOMClassifier" type, we use a base unit, and no conversion is needed. If the unit of measurement is taken from the "UOM" catalog, that amount is recalculated with the account of "Ratio."

Let us start the application in dialog mode and check the code in action. Post the previously created document again and check the record in the "Inventory" register.

image_32.png

As you can see, the document value stated in grams has been converted into kilograms when added to the register.

We can run an additional check by adding one more line for the same item of goods but select a ton as a unit of measurement. Do it:

image_33.png

 

image_34.png

Well, the code works perfectly fine: one ton has been converted into one thousand kilograms.

This is basically it. We have learned a way to handle several units of measurement for a single item of goods using standard mechanisms of the 1C platform.

And we still have a lot of standard functionality to discuss. Stay tuned. Even more fascinating things are on the way.

As usual, I am glad to share the link to the code we have used in this article. Enjoy!

Be the first to know tips & tricks on business application development!

A confirmation e-mail has been sent to the e-mail address you provided .

Click the link in the e-mail to confirm and activate the subscription.