1C:Professional training course. Lesson 5-4

Lesson 5-4

Repricing assistance

Now we have an opportunity to help a user making the right call when repricing Materials. We know the current item price of any Material at any moment of time, so we can check whether the new price set by a user is profitable or not.

We could add a new column into this table and fill it out with a cost of correspondent material effective on the date of the document. We could also check if the new price and if it’s less than the materials cost we could warn a user and give him a chance to fix it.

So, let’s do it.


And this is a resulting source code we’ve got in the PriceChange document form’s module:

&AtServer
Procedure OnCreateAtServer(Cancel, StandardProcessing)
	RecalcCosts();
EndProcedure
&AtServer
Procedure RecalcCosts()
	For Each Material In Object.MaterialsAndServices Do
		Material.Cost = GetCurrentCost(Material.MaterialOrService, Object.Date)
	EndDo;
EndProcedure
&AtServer
Function GetCurrentCost(Material, Date)
	
	//Exclude services
	If Material.MaterialOrService = Enums.MaterialOrService.Service Then 
		Return 0;
	EndIf;
	
	//Get the cost for the date of the document
	Query = New Query;
	Query.Text = 
		"SELECT
		|	MaterialsCostBalance.CostBalance AS TotalCost,
		|	MaterialsCostBalance.QuantityBalance AS Quantity
		|FROM
		|	AccumulationRegister.MaterialsCost.Balance(&Date, 
		|		Material = &Material) AS MaterialsCostBalance";
	
	Query.SetParameter("Date", Date);
	Query.SetParameter("Material", Material);
	
	QueryResult = Query.Execute();
	
	SelectionDetailRecords = QueryResult.Select();
	
	If SelectionDetailRecords.Next() then
		If SelectionDetailRecords.Quantity > 0 Then
			Return(SelectionDetailRecords.TotalCost / SelectionDetailRecords.Quantity);
		EndIf;
	EndIf;
	Return 0;
EndFunction
&AtClient
Procedure MaterialsAndServicesMaterialOrServiceOnChange(Item)
	Item.Parent.CurrentData.Cost = 
		GetCurrentCost(Item.Parent.CurrentData.MaterialOrService, Object.Date);
EndProcedure
&AtClient
Procedure DateOnChange(Item)
	RecalcCosts();
EndProcedure

Posting Services document to MaterialsCost register

We can think about the MaterialsCost register as of financial representation of our Warehouses’ balances. While the BalanceOfMaterials register knows how many things left, the new MaterialCost register knows how many we have paid for what is left. In other words, it knows how much many (in a form of sellable goods) we have left in the Warehouses.

When we buy new Materials (register a new GoodsReceipt document), we add some amount of money to the MaterialsCost register. In the same way, we need to subtract some amount of money when we sell materials registering the Services document.

The posting of this document affects the current Material’s cost of course. If we sell a Material cheaper than its current cost, we increase it. If we sell it more expensively, we reduce it. Let’s visualize this process for better understanding. This is a table showing a process of buying and selling one specific Material over time.


Over the period from T1 to T3, we buy 30 items of Material with the item price constantly going up (from $1 to $2). By the end of this period, the average item cost is $1.5. At the moment T4, we make a bad call and sell 20 items for $1 each ($0.5 per unit cheaper that it cost us). It causes the item cost spiking up to $2.5. We fire the salesperson responsible for the incident and start increasing the selling price. After we sell 5 items for $4 each the average cost gets back to $1. Then we sell one more item for $5 and now the cost is $0, and after the next sell (2 items for $3 each) it goes down to -$2.5, meaning that now we can afford given the Material away for free and still have profit.

To implement this posting we need to do a few things. Tell the platform that the Services document posts records to the MaterialsCost register.


Then we need to change the Posting event handler source code (here we give only the first fragment of the code).

Procedure Posting(Cancel, Mode)
	
	// 1. Filling out the registers' recordsets
	For Each CurRowMaterialsAndServices In MaterialsAndServices Do
		
		RegisterRecords.BalanceOfMaterials.Write = True;
		RegisterRecords.MaterialsCost.Write = True;
		
		If CurRowMaterialsAndServices.MaterialOrService.MaterialOrService  
			Enums.MaterialOrService.Service Then
			
			// 1.1. Filling out BalanceOfMaterials recordset
			Record = RegisterRecords.BalanceOfMaterials.Add();
			Record.RecordType = AccumulationRecordType.Expense;
			Record.Period = Date;
			Record.Material = CurRowMaterialsAndServices.MaterialOrService;
			Record.Warehouse = CurRowMaterialsAndServices.Warehouse;
			Record.Quantity = CurRowMaterialsAndServices.Quantity;
			
			// 1.2. Filling out MaterialsCost recordset
			Record = RegisterRecords.MaterialsCost.Add();
			Record.RecordType = AccumulationRecordType.Expense;
			Record.Period = Date;
			Record.Material = CurRowMaterialsAndServices.MaterialOrService;
			Record.Quantity = CurRowMaterialsAndServices.Quantity;
			Record.Cost = CurRowMaterialsAndServices.Quantity * CurRowMaterialsAndServices.Price;
			
		EndIf;
		
	EndDo;

After that, the functionality should start working. To check the results, you need to repost all Services documents and take a look at the MaterialsCost register. All Materials you have sold have to appear in the register marked with red minus signs.


Selling materials assistance

When a user is filling out the Services document current materials’ and services’ prices are obtained from the Prices information register. As soon as we implemented a repricing check procedure we can be sure that these prices are meaningful. But a user still can change this default price and, therefore, can make an unintentional mistake.

We could make the price field read-only and prevent a user from changing a selling price,but it looks like an important functionality. One standard price can be not applicable to all situations and sometimes a user just needs to change it. But we need at least warn a user if the selling price he set is lower than the cost of the Material. It will let him make an informed decision. So, let’s implement the same cost-checking functionality for Services document.

The GetCurrentCost function we used in PriceChange document’s form is perfectly fit to be reused in Services documents form, so let’s move it to the common module.


Make sure that you selected the Server and the Server calls checkboxes. The first one means that the modules procedures and functions are run on the server-side and the second allows them to be called from the client-side.

Place the function in the module and add keyword “Export” to the function definition. As you should remember from the Lesson 2, this keyword allows the procedure or function to be called from the outside of the module.

Function GetCurrentCost(Material, Date) Export

Then, to implement this functionality in Services document’s form we need to repeat the same steps we did for the PriceChange document’s form:

  • Add a new tabular section attribute Cost (Number) to forms attributes, place it on the form and make read-only;
  • Define the same conditional appearance for the form;
  • Add cost acquirement in OnCreateOnServer and MaterialsAndServicesMaterialOrServiceOnChange event handlers;
  • Implement new DateOnChange event handler. Here are all changed procedures of the forms.

Here is a new form module’s text with all changes needed.

&AtClient
Procedure MaterialsAndServicesQuantityOnChange(Item)
	Material = Item.Parent.CurrentData;
	Material.Total = Material.Price * Material.Quantity;
EndProcedure
&AtServer
Procedure OnCreateAtServer(Cancel, StandardProcessing)
	For Each Material In Object.MaterialsAndServices Do
		Material.Total = Material.Price * Material.Quantity;
		Material.Cost = GeneralPurpose.GetCurrentCost(Material.MaterialOrService, Object.Date)
	EndDo;
EndProcedure
&AtClient
Procedure MaterialsAndServicesMaterialOrServiceOnChange(Item)
	Item.Parent.CurrentData.Price = 
		GetCurrentPrice(Object.Date, Item.Parent.CurrentData.MaterialOrService);
	Item.Parent.CurrentData.Cost = 
		GeneralPurpose.GetCurrentCost(Item.Parent.CurrentData.MaterialOrService, 
		Object.Date);
EndProcedure
&AtServer
Function GetCurrentPrice(Date, Ref)
	Filter = New Structure;
	Filter.Insert("MaterialOrService", Ref);
	CurrentPrice = InformationRegisters.Prices.GetLast(Date, Filter); 
	Return CurrentPrice.Price;
EndFunction
&AtClient
Procedure DateOnChange(Item)
	RecalcCost();
EndProcedure
&AtServer
Procedure RecalcCost()
	For Each Material In Object.MaterialsAndServices Do
		Material.Cost = GeneralPurpose.GetCurrentCost(Material.MaterialOrService, Object.Date)
	EndDo;
EndProcedure


 Lesson 5-3 | Course description | Lesson 5-5

1C:Enterprise Developer's Community