In various applications, the task regularly arises of supporting the logic of the change in time of some attribute of an object relative to a certain subject (or subjects). For example, this may be a change in the retail price of goods in stores or KPI indicators for employees.
In this article I will show what domain logic and interfaces can be built to solve this problem. I must make a reservation right away that it will concern the managerial influence of the user on the attribute, and not the reflection of historical change.
The implementation will be presented on the basis of the open and free
lsFusion platform, but a similar scheme can be applied when using any other technology.
Introduction
For a simpler presentation and understanding of the article, we take the price as an attribute, the product as the object, and the warehouse will be the subject. In this case, the minimum possible interval for setting the attribute will be the date. Thus, the user will be able to determine what the price for a particular date will be for any product and warehouse.
The user input scheme for price changes will be similar to that used in classic version control systems. Any change, from the point of view of domain logic, will be a single
commit , on the basis of which the state for a certain date will be calculated. In many subject areas, such commits are called documents or transactions. In this case, by this commit we will mean the so-called price list. Each price list will specify the goods and warehouses that are included in it, as well as the validity period.
The described scheme has the following advantages:
- Atomicity . Each change is issued as a separate document. Therefore, these documents can be temporarily saved, but not posted. If you mistype it, itโs easy to roll back the entire change.
- Transparency It is easy to determine who made the change and when, as well as indicate the reason by making a comment on the document.
The main difference from the version control system is that explicit commits are independent of each other. Thus, it is possible to delete all commits relatively painlessly at any time. In addition, each such commit can be set to end when it ceases to function, which of course is not in the version control system.
Implementation
We begin the definition of domain logic with warehouses. Let's complicate the solution a bit by combining the warehouses into a hierarchy of a dynamic depth group. By what principle this is done is described in the corresponding
article , so Iโll just give a piece of code that declares groups and creates forms for editing them:
Warehouse Group Announcement
Next, declare warehouses that can be tied to any of the groups:
And finally, declare the logic of the goods:
We proceed directly to creating the logic of price lists. First, we set the
price list class itself, as well as its validity period:
We believe that if no
Date has been set, then the price list is endless.
We add an event that, when creating the price list, will automatically put down the current date from which it will begin to operate.
The
LOCAL keyword means that the event will not fire when the save is applied to the database, but immediately at the time of the change.
Then add the user who created it and the creation time:
Now create an event that will automatically fill them:
This event, unlike the previous one, will be triggered only when the Save button is clicked. That is, during a save transaction to the database.
Next, create the price list lines in which the goods and prices will be set:
The
NONULL attribute indicates that the
priceList property
should always be set, and
DELETE indicates that when the property value is zeroed (for example, when deleting the price list), the corresponding line should be automatically deleted.
For future use, create properties that will determine the period of validity of the price list lines:
Now we will bind the price list to the warehouses for which it will operate. First, add the primary property, which will be true if the entire group of warehouses is included in the price list:
We calculate the โinclusionโ of the group taking into account the selected parents (as described in the article about hierarchies):
Add the primary property, with which you can specify that the price list acts on a specific warehouse:
We calculate the final property, which will determine that the price list changes prices in the corresponding warehouse, taking into account the groups:
Create a property that will show the names of all selected groups and warehouses of the price list, for a more convenient user to view the list of price lists:
The final step in the description of domain logic will directly calculate the current price of the goods in the warehouse. To do this, create a property that finds the last by date line of the price list with the desired goods, warehouse and validity period:
In the logic of calculating this property, various variations are possible. You can change both the filter for hitting rows (for example, adding a condition in
WHERE that the price list is posted) and the order. It should be noted that the object itself, or rather its internal identifier, has been added to the selection order as the second parameter. This is necessary so that the price value is always determined in a unique way.
Based on the received price list line, we determine the price value and its validity period:
They will be further used in user interface tables.
Next, we move on to building the user interface. First, we draw a form for editing the price list. Create a form and add the โheaderโ of the document there:
Add the price list line to the form:
Next, add a tree in which there will be both groups and warehouses:
Properties for groups and warehouses are added to the tree at the same time. The platform will, depending on the object, show this or that property in the order they are added to the form.
We customize the design of the form so that goods and warehouses are drawn in separate tabs:
The edit form will look like this:
It remains to build the basic form of price management. It will consist of two tabs. The first one will show a list of all price lists (similar to the list of commits). The second tab will display the current prices for a particular warehouse for the selected date.
To implement the first tab, add to the form a list of price lists with lines for a quick preview:
For the second tab, we first add the date on which prices are displayed, the tree of warehouse groups, as well as the warehouses themselves:
The list of warehouses will show all warehouses that are descendants of the group selected at the top.
Next, add to the form a list of goods for which there are valid prices for the warehouse on the selected date:
Both the price itself and the validity period are added to the columns. You can also add the price list number - then this table will resemble the logic of annotations in version control systems.
In order for the user to understand where such a price came from, we add down the list of price list lines with suitable goods and warehouses:
Using the
BACKGROUND attribute
, highlight the row that determined the price shown in the table.
Also, for the convenience of the user, we will add the ability to open the edit form of the corresponding price list in a new session immediately from this story:
To achieve this, you need to specify the action that will be performed when you try to edit a line by implementing the built-in
edit action. Then, a standard button for editing an object through a dialog call is added to the form in the standard way.
And finally, we form the final form design:
Here, the
pane container is added first, which consists of two tabs:
priceLists and
prices . The first of them just adds a list of price lists and lines. In the second, two panels are created:
leftPane and
rightPane . The left panel contains the date and warehouses, and the right panel contains the goods and price history.
Result
Consider the main options for using the resulting logic.
Suppose we have two separate price lists for different groups of products. Then, depending on the selected warehouse, in the price tab, only goods from the corresponding price lists will be displayed:
Now create a new price list with a limited validity period, a stripped down list of warehouses and a new price. On the second tab, if we select a date in the range of the new price list, we will get a new price from it. As soon as the validity period expires, the old price will again return from the original price:
Using the same mechanism, you can โcancelโ the action of specific prices from a certain date. For example, if you enter a new price without specifying a price, it turns out that the price will be reset and the goods will disappear from the filter. In this case, when deleting the entered document, everything returns to the old state:
The property obtained with the price of the goods by the warehouse on the date can be further used in various events or other forms. For example, you can make automatic pricing in an order based on this pricing logic:
A nice bonus in this logic will be that when you add a new warehouse to the group, prices from already created price lists will automatically apply to it. The same thing will happen when you change the group for the warehouse.
If you wish, you can make the column with the price on the tab with current prices editable and add a button that will create a new commit for the changed prices.
Conclusion
In the solution at the platform level, neither reference books, nor documents with strings, nor registers, nor reports and other unnecessary abstractions are used. Everything is done exclusively on the concepts of classes and properties. Note that this rather complex logic was implemented in approximately 150 significant lines of code on lsFusion. To implement it in the same setting on other platforms (for example, 1C) is a much more difficult task.
The scheme described above is widely used in the lsFusion-based
ERP solution . With it, with various modifications, price lists of suppliers, management retail prices, stocks and many other management parameters are supported.
The template can be complicated by adding several entities to the document (for example, a supplier can be added to the warehouse), as well as defining several attributes in one document at once. In particular, you can add the entity Price Type, and in the document line set the price for the tuple of the line and the corresponding price type. In the logic described above, you just need to add a few additional parameters to some properties.
With the help of several additional lines of code, it is possible to denormalize all change records into one table on which to build the corresponding index. Then any value for any date will be sampled in a logarithmic time. Such optimization is necessary when there are several hundred million records in this table.
You can try the built example online on the corresponding
page of the site (section Platform). Here is the whole source code that you need to paste into the desired field: