function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Ben Merton 15Ben Merton 15 

Building my first trigger

I am trying to write an Apex class that clones a record from a related record.  My app structure is a little complex:

Work_Order__c (Parent of Material Indent)
Custom Fields:  Quantity__c, Delivery Date__c
Custom Lookup:  Product__c

Material_Indent__c (Child of Work Order Object)
Custom Fields:  Quantity__c
Custom Lookup:  GSI_No__c, Work_Order__c

Product__C Object (Parent of BOM Line Item Object)
Custom Fields:  Irrelevant

BOM_Items (Child of Parent Product Object)
Custom Fields: Quantity__c
Custom Lookup: GSI_No__c

I am tryig to write a trigger that automates the process of creating an Indent from the BOM Items (which are related to the Product, which is in turn in the lookup in the Work Order).

The trigger will be initiated from the Work Order page with a button ("Create Indent").

This is where I am so far.  I am confused about whether I am even heading in the right direction?  I have doubts about the following:

1.  Is a trigger even a way to go for this?
2.  Will Page Reference return the Products__c custom field ID if I am running this via a button on the Work Order (Products__c is a Custom Field on Work_Order__c
3.  Is the method for creating a set of the BOM Items which have a Products__c=the Products__c found from the Page Reference on the Work Order correct?
4.  Am I correctly traversing the objects using BOMItemSet.add(BOMItems.GSI_No__c.id) to add the ID of the GSI number to the list?
 
trigger Indent on Work_Order__c (before update) {

 //   Create variable for capturing Product ID

Page Reference productid=Product__c.id; 

  // Create a set of all records

  Set<String> BOMItemSet = new Set<String>();
  for (BOM_Items__c BOMItems : Trigger.new) {

// Add the fields for Quantity and GSI No.

    if (BOMItems.Product__c = productid) {
      BOMItemSet.add(BOMItems.Quantity__c);
      BOMItemSet.add(BOMItems.GSI_No__c.id);  //NB This is a lookup
  }

After this, I was going to create another trigger to insert the set that I have created above into Material_Indents, the child record of the Product object.  However, I don't want to go on with this unless I am clear with the first step!

Please help!




 
Best Answer chosen by Ben Merton 15
Krishna SambarajuKrishna Sambaraju
Hi Ben,

Here is how you can do it with a class and a visualforce page.
Class: CreateIndentController
public class CreateIndentController{
	string workOrderId;
	string productId;
	public CreateIndentController()
	{
		workOrderId = ApexPages.currentPage().getParameters().get('id');
		productId = ApexPages.currentPage().getParameters().get('productId');
	}
	
	public pageReference onLoad()
	{
		if (string.IsBlank(productId))
		{
			ApexPages.addMessage(new ApexPages.message(ApexPages.Severity.ERROR, 'No product selected on the Work Order'));
            return null;
		}
		List<BOM_Items__c> bomItems = [select Quantity__c, GSI_No__c from BOM_Items__c where Product__c = :productId];
		if (bomItems.size() == 0)
		{
			ApexPages.addMessage(new ApexPages.message(ApexPages.Severity.ERROR, 'No BOM Items found for the product'));
            return null;
		}
		List<Material_Indent__c> indents = new List<Material_Indent__c>();
		for (BOM_Items__c item : bomItems)
		{
			Material_Indent__c indent = new Material_Indent__c();
			indent.Work_Order__c = workOrderId;
			indent.Quantity__c = item.Quantity__c;
			indent.GSI_No__c = item.GSI_No__c;
			indents.add(indent);
		}
		if (indents.size() > 0)
		{
			insert indents;
		}
		return new PageReference('/' + workOrderId);
	}
}
Visualforce page: CreateIndentPage (which uses the above class).
<apex:page controller="CreateIndentController" action="{!onLoad}">
    <apex:pageMessages />
</apex:page>
Edit the button you created and select the behaviour as "Display in existing window without sidebar or header" and the content source as "URL" and in the text area specify the url as below.
/apex/CreateIndentPage?id={!Work_Order_c.Id}&productId={!Work_Order__c.Product__c}

In the class I have set some validations, when the product is blank on Work Order and when there are no BOM Items for that product, which will display the error message on the page. Then the user need to add the product or BOM items accordingly and then come back to work order and click the button again, which will then create the Material indents.

Hope this is helpful.
 

All Answers

Krishna SambarajuKrishna Sambaraju
You should go for the trigger when you want something to happen automatically when you insert, update or delete a record. In your case you are clicking a button, so you can create a webservice and call the webservice from the javascript on the button, or create an apex class and use this class as a controller or extension in  a visualforce page. And on your custom button "Create Indent" select the behaviour as "Display in existing window without sidebar or header" and content source as "URL" and select the url in the text area (for example: /apex/<your_page>?id=<work_order_id>&productId=<product_id>.

Change the field names accordingly.

Hope this helps.
Prosenjit Sarkar 7Prosenjit Sarkar 7
Hi Ben,

Yes you can go with trigger.

I think your should be like this,
 
trigger Indent on Material_Indent__c (after insert) {
		if(Trigger.isInsert && Trigger.isAfter ) {
			IntendTriggerHandler.createBomItems(Trigger.New);
		}
}

and your handler class should be like this,
 
class IntendTriggerHandler {
	public void createBomItems(List<Material_Indent__c> miList) {
		List<BOM_Items__c> biList = new List<BOM_Items__c>();
		List<Material_Indent__c> miListWithProduct = [SELECT Work_Order__r.Product__c FROM Material_Indent__c WHERE Id IN :miList];
		Map<Id, Id> miProdMap = new Map<Id, Id>();
		for(Material_Indent__c mi : miListWithProduct) {
			miProdMap.put(mi.Id, mi.Work_Order__r.Product__c);
		}
		for(Material_Indent__c mi : miList) {
			BOM_Items__c bi = new BOM_Items__c(Quantity__c=mi.Quantity__c, GSI_No__c=mi.GSI_No__c, Product__c=miProdMap.get(mi.Id));
			biList.add(bi);
		}
		insert biList;
	}
}

At first you have to write & save the IntendTriggerHandler class and then you should write the apex trigger.

apart from trigger you may try with process builder also.

Thanks
Prosenjit 
 
Ben Merton 15Ben Merton 15
Prosenjit thanks for your answer, although I still don't understand WHY it is correct to use a trigger as Krishna is saying otherwise.

Suppose I want to convert your handler class to a regular class called by a button/visualforcepage/javascript, would i need to change anything?

Ben
Ben Merton 15Ben Merton 15
Prosenjit

Further to this, I THINK your code may have misunderstood the structure

I am looking to populate the Indent FROM the BOM Item.  I think that your code goes the other way round (ie creating a BOM Item FROM the Indent).  If this is correct, please like this post, as I am feeling devoid of likes as a newbie and I need all the electronic support I can get...

Ben
Krishna SambarajuKrishna Sambaraju
Hi Ben,

If you want to automatically create the indent records when a work order is saved, then you can go for the trigger. Then you don't need to click a button. When the work order is saved, the trigger will automatically create the indent records. Here is how you can build the trigger.
trigger CreateIndent on Work_Order__c(after insert, after update)
{
	Map<Id, Work_Order__c> productId2WorkOrder = new Map<Id, Work_Order__c>();
	for (Work_Order__c wo : Trigger.new)
	{
		if (wo.Product__c != null)
		{
			productId2WorkOrder.put(wo.Product__c, wo);
		}
	}
	if (!productId2WorkOrder.isEmpty())
	{
		List<BOM_Items__c> allBOMItems = [select Quantity__c, GSI_No__c from BOM_Items__c where Product__c IN :productId2WorkOrder.keySet()];
		Map<Id, List<BOM_Items__c>> productId2BOMItems = new Map<Id, List<BOM_Items__c>>();
		
		for (BOM_Items__c bom : allBOMItems)
		{
			List<BOM_Items__c> bomItems = productId2BOMItems.get(bom.Product__c);
			if(bomItems == null)
			{
				bomItems = new List<BOM_Items__c>();	
			}
			bomItems.add(bom);
			productId2BOMItems.put(bom.Product__c, bomItems);
		}
		List<Material_Indent__c> indents = new List<Material_Indent__c>();
		for (Work_Order__c wo : productId2WorkOrder.values())
		{
			List<BOM_Items__c> items = productId2BOMItems.get(wo.Product__c);
			for (BOM_Items__c item : items)
			{
				Material_Indent__c indent = new Material_Indent__c ()
				indent.Work_Order__c = wo.Id;
				indent.Quantity__c = item.Quantity__c;
				indent.GSI_No__c = item.GSI_No__c;
				indents.add(indent);
			}
		}
		if (indents.size() > 0)
		{
			insert indents;
		}
	}
}
Change or adjust the fields according to your requirement. Hope this helps.
 
Ben Merton 15Ben Merton 15
Krishna

Many thanks.  I am definitely not looking for it to run on save.  Only when the button is pressed.  The reason for this is that they may want to create a Work Order independently of issuing the material (for example if the BOM in the product isn't prepared at that point, the production team will still need to start other activities).

Ben
Krishna SambarajuKrishna Sambaraju
Hi Ben,

Here is how you can do it with a class and a visualforce page.
Class: CreateIndentController
public class CreateIndentController{
	string workOrderId;
	string productId;
	public CreateIndentController()
	{
		workOrderId = ApexPages.currentPage().getParameters().get('id');
		productId = ApexPages.currentPage().getParameters().get('productId');
	}
	
	public pageReference onLoad()
	{
		if (string.IsBlank(productId))
		{
			ApexPages.addMessage(new ApexPages.message(ApexPages.Severity.ERROR, 'No product selected on the Work Order'));
            return null;
		}
		List<BOM_Items__c> bomItems = [select Quantity__c, GSI_No__c from BOM_Items__c where Product__c = :productId];
		if (bomItems.size() == 0)
		{
			ApexPages.addMessage(new ApexPages.message(ApexPages.Severity.ERROR, 'No BOM Items found for the product'));
            return null;
		}
		List<Material_Indent__c> indents = new List<Material_Indent__c>();
		for (BOM_Items__c item : bomItems)
		{
			Material_Indent__c indent = new Material_Indent__c();
			indent.Work_Order__c = workOrderId;
			indent.Quantity__c = item.Quantity__c;
			indent.GSI_No__c = item.GSI_No__c;
			indents.add(indent);
		}
		if (indents.size() > 0)
		{
			insert indents;
		}
		return new PageReference('/' + workOrderId);
	}
}
Visualforce page: CreateIndentPage (which uses the above class).
<apex:page controller="CreateIndentController" action="{!onLoad}">
    <apex:pageMessages />
</apex:page>
Edit the button you created and select the behaviour as "Display in existing window without sidebar or header" and the content source as "URL" and in the text area specify the url as below.
/apex/CreateIndentPage?id={!Work_Order_c.Id}&productId={!Work_Order__c.Product__c}

In the class I have set some validations, when the product is blank on Work Order and when there are no BOM Items for that product, which will display the error message on the page. Then the user need to add the product or BOM items accordingly and then come back to work order and click the button again, which will then create the Material indents.

Hope this is helpful.
 
This was selected as the best answer
Ben Merton 15Ben Merton 15
Krishna - wow.  Thank you.  This looks great.  However, I think that there is a problem with the Javascript as it isn't passing any parameter from the page into the onLoad() method.  Could this be to do with namesspaces.  If I have a namespace abc__, would this need to appear in the strings above?  As in 
/apex/CreateIndentPage?id={!abc__Work_Order_c.Id}&productId={!abc__Work_Order__c.Product__c}
Ben Merton 15Ben Merton 15
Krishna - ignore the last post (apart from wow.  Thank you.  This looks great)!

I ran the debug log on it and it is returning the Work Order ID fine, but the Product ID is getting returned as the Name and not the ID.

I think it is definitely something wrong with this...
         workOrderId = ApexPages.currentPage().getParameters().get('id'); //THIS IS RETURNING 1a0M1500000UGHdo
        productId = ApexPages.currentPage().getParameters().get('productId');  //THIS IS RETURNING PRO-0001

Could there be some sort of conflict between the strings declared at the top and the parameters used in the Javascript?
Krishna SambarajuKrishna Sambaraju
Is Product__c a lookup field on Work_Order__c object? Then it should get the Id and not the Name.
Ben Merton 15Ben Merton 15
It is a lookup...but it is definitely returning the Name, and the code is throwing the error at bomItems.size() because it is != 0 as opposed to ==

Even if I bypass this, the code is not inserting any records for the same reason.
Krishna SambarajuKrishna Sambaraju
OK. Do this. Edit the button again, highlight the "{!abc__Work_Order__c.Product__c}". Then in the "Select the Field Type" just above the Text area, check if you can see your custom object "Product", select it and then from "Insert field" dropdown select the Id. This should get the id of the Product and should work.
Ben Merton 15Ben Merton 15
Yes I had tried that earlier.  It returns Product__c.Id.  But when I put in Work_Order__c.Product__c.Id it says it doesn't exist when you try to save the button.
Krishna SambarajuKrishna Sambaraju
Basically you need to replace {!abc__Work_Order__c.Product__c} with {!abc__Product__c.Id}. Hope this helps.
 
Ben Merton 15Ben Merton 15
No.  Now the error in the Debug log is saying that the Product ID is blank, and it is returning the 'No Product selected on the Work Order' error.
Ben Merton 15Ben Merton 15
I am pretty sure it's something to do with the dot notation in Javascript.  The id for the Work Order is fine....
Krishna SambarajuKrishna Sambaraju
The way to get around this is create a formula field "ProductId" on Work Order with a value abc__Product__r.Id to get the Id of the Product and use that formula field on the button. This will work for sure.
Prosenjit Sarkar 7Prosenjit Sarkar 7
Hi Ben,

I have written code for creating BOM items from Intend. Yes you can use this apex code to attach with a visualforce. But for calling this from a javascript button you need to change the class a little bit. An apex class should be gobal and methods should be as Webservice.

please follow this link for that : http://prosenjit-sarkar-sfdc.blogspot.in/2015/01/how-to-call-apex-method-through-custom.html  (http://prosenjit-sarkar-sfdc.blogspot.in/2015/01/how-to-call-apex-method-through-custom.html)

Thanks
Prosenjit.