Sales force trigger INVALID_FIELD_FOR_INSERT_UPDATE - salesforce

Below code works fine when updating/inserting on Visual Force Page one record at a time, receive error 'Insert failed....INVALID_FIELD_FOR_INSERT_UPDATE, cannot specify Id in an insert call' when using data loader (error pointing to code, 'insert amcRecord'). Would anyone know how to fix?
trigger Status on Cl__c (after insert, after update) 
{
List<AMC__c> amcRecord = new List<AMC__c>();
for (Cl__c cls: Trigger.new ) 
{
    Cl__c oldcls = Trigger.oldMap.get(cls.Id);
    if (cls.Status__c == 'Completed' && (oldcls.Status__c != 'Completed'  )) 
{
     AMC__c newAMC = new AMC__c();
     newAMC.Cl__c = cls.ID;
     newAMC.Default__c = true;  
     amcRecord.add(newAMC); 
     insert amcRecord;
}
  }
}

amcRecord is a list, not a single item, but you're calling insert for every item in the request [which would only be 1 for the UI, but upto 200 with the data loader. You need to move the insert amcRecord line to after the for (... Trigger.new) loop has finished, so that its only called once, e.g.
trigger Status on Cl__c (after insert, after update)
{
List<AMC__c> amcRecord = new List<AMC__c>();
for (Cl__c cls: Trigger.new )
{
Cl__c oldcls = Trigger.oldMap.get(cls.Id);
if (cls.Status__c == 'Completed' && (oldcls.Status__c != 'Completed' ))
{
AMC__c newAMC = new AMC__c();
newAMC.Cl__c = cls.ID;
newAMC.Default__c = true;
amcRecord.add(newAMC);
}
}
insert amcRecord;
}

You shouldn't be performing DML inside a loop.This exception comes if you try to insert already inserted record.
Account a =new Account();
a.name='Hello';
insert a;
insert a;

Changed
insert amcRecord;
to
upsert amcRecord;
It seems to be working now.

Related

How to write a test class for an apex trigger

Can someone explain to me how to write a test class for an apex trigger like the following one?
trigger LeadAssignmentTrigger on Broker__c (before insert,before update)
{
List<Broker__c > leadsToUpdate = new List<Broker__c >();
for (Broker__c broker: Trigger.new)
{
if (broker.Referral_ID__c!= NULL)
{
String str = broker.Referral_ID__c;
Integer ln = str.Length();
String likeStr = '%'+str.subString(ln-10, ln-7)+'%'+str.subString(ln-7, ln-4) +'%'+ str.subString(ln-4);
// Find the sales rep for the current zip code
List<User> zip = [select Id from User
where MobilePhone Like : likeStr];
// if you found one
if (zip.size() > 0)
{
//assign the lead owner to the zip code owner
broker.OwnerId = zip[0].Id;
leadsToUpdate.add(broker);
}
else
{
// Throw Error
broker.addError('Invalid Referrel ID');
}
}
}
}
I am new to salesforce.Anyone help me to how to write apex class(test class) for above trigger.
#isTest
private class LeadAssignmentTriggerTest
{
static testMethod void validateHelloWorld()
{
User userObj = new User( Id = UserInfo.getUserId() );
userObj.MobilePhone = '5555555555';
update userObj
test.startTest();
try
{
Broker__c broker = new Broker__c();
broker.Referral_ID__c = '55555555';
broker.City ='New York';
// Add all required field here
insert broker;
}
Catch(Exception ee)
{
}
test.stopTest();
}
}
AccountBrowseExtensionTesttestAccountBrowseSystem.DmlException: Insert failed. First exception on row 0; first error: FIELD_CUSTOM_VALIDATION_EXCEPTION, City is mandatory: []
Stack Trace: Class.AccountBrowseExtensionTest.testAccountBrowse: line 20, column 1
CloseActivityControllerTesttestCloseActivitySystem.DmlException: Insert failed. First exception on row 0; first error: FIELD_CUSTOM_VALIDATION_EXCEPTION, City is mandatory: []
Stack Trace: Class.CloseActivityControllerTest.testCloseActivity: line 13, column 1
changeOwnerControllerTesttestchangeOwnerSystem.DmlException: Insert failed. First exception on row 0; first error: FIELD_CUSTOM_VALIDATION_EXCEPTION, City is mandatory: []
Stack Trace: Class.changeOwnerControllerTest.testchangeOwner: line 20, column 1
cntactsclassTesttestcntactsSystem.DmlException: Insert failed. First exception on row 0; first error: FIELD_CUSTOM_VALIDATION_EXCEPTION, City is mandatory: []
Stack Trace: Class.cntactsclassTest.testcntacts: line 13, column 1
LogACallControllerTesttestLogACallSystem.DmlException: Insert failed. First exception on row 0; first error: FIELD_CUSTOM_VALIDATION_EXCEPTION, City is mandatory: []
Stack Trace: Class.LogACallControllerTest.testLogACall: line 14, column 1
RedirectControllerTesttestRedirectSystem.DmlException: Insert failed. First exception on row 0; first error: FIELD_CUSTOM_VALIDATION_EXCEPTION, City is mandatory: []
Stack Trace: Class.RedirectControllerTest.testRedirect: line 20, column 1
TestAccountSharetestSystem.DmlException: Insert failed. First exception on row 0; first error: FIELD_CUSTOM_VALIDATION_EXCEPTION, Mobile Number is mandatory after Appointment is fixed.: []
Stack Trace: Class.TestAccountShare.test: line 40, column 1
You are writing a trigger for Broker__c on before insert and before update
So since it's a trigger, your code will run every time you insert or update a record.
To write a test class simply create two test methods:
one to insert a Broker__c record
one to update a Broker__c record
Check here on how to create test classes
By the way you should check the best coding practices on how to write a better trigger and handlers here
Edit:
You should also remove the SOQL inside the loop and create a Map with your query outside the for loop

simple trigger for duplicate fullname not working,

In student123__C detail page there are 3 fields: firstname__c. lastname__c, middlename__c. I need to write a trigger to check "if a person is entering the same values then, throw an error that "duplicate contact found".
Example: 1st record I entered as " Siva Naga Raju " so if am again entering this same name then it should throw an error.
For that i created a forumla field called TOTALNAME__C ( firstname__c + lastname__c + middlename__c). upto here ok. But trigger not firing, intially i worte bulk trigger, but its not firing, so i wrote a simple trigger then, it is also not firing, please some boby help me. thanks in advance.
trigger duplicatefullname on student123__c (before insert, before update) {
string name;
list<student123__c> databasenames;
for (student123__c stu : trigger.new) {
name = stu.firstname__c + stu.lastname__c + stu.middlename__c;
databasenames = [select totalname__C from student123__c where totalname__C = :name];
if (databasenames.size() > 0)
stu.adderror('another person with duplicate full name found');
}
}
Basically, formula doesn't store any value. Formula is executed only when you retrieve it.
A formula is similar to an equation that is executed at run time.

Codeigniter, error tracking in transaction

I'm running a little method in CodeIgniter to insert some lines in the database (same table). I would like to see which insertion have failed inside a transaction (by returning an array of titles). My code is :
$failure = array(); //the array where we store what failed
$this->db->trans_start();
foreach ($data as $ressourceCsv){ //data is an array of arrays to feed the database
$this->ajout_ressource($ressourceCsv); //method to insert (basically, just an insert with active record)
if (($this->db->_error_message())!=null) {
$failure[] = $ressourceCsv['title'];
}
}
$this->db->trans_complete();
return $failure;
The fact is that if I don't make it a transaction (no $this->db->trans_...), it works perfectly and I have an array containing a few titles. But with the transaction, the array contains every titles since the first error. Is there a way to just get the title from the insertion which caused the transaction to rollback?
I have also tried with :
$failure = array(); //the array where we store what failed
$this->db->trans_start();
foreach ($data as $ressourceCsv){ //data is an array of arrays to feed the database
if (!$this->ajout_ressource($ressourceCsv)) { //active record insertion return true
$failure[] = $ressourceCsv['title']; // if successful
}
}
$this->db->trans_complete();
return $failure;
I believe that once an error occurs inside a transaction, you must rollback before any more DB mods can be made. That would explain the behavior you are seeing. After the first error, the transaction is "aborted" and you continue your loop, causing every subsequent SQL command to fail as well. This can be illustrated as follows:
db=# select * from test1;
id | foo | bar
----+-----+-----
(0 rows)
db=# begin;
BEGIN
db=# insert into test1 (foo, bar) values (1, 'One');
INSERT 0 1
db=# insert into test1 (foo, bar) values (Oops);
ERROR: column "oops" does not exist
LINE 1: insert into test1 (foo, bar) values (Oops);
^
db=# insert into test1 (foo, bar) values (2, 'Two');
ERROR: current transaction is aborted, commands ignored until end of transaction block
db=# select * from test1;
ERROR: current transaction is aborted, commands ignored until end of transaction block
db=# commit;
ROLLBACK
ace_db=# select * from test1;
id | foo | bar
----+-----+-----
(0 rows)
db=#
Note it seems that "commit" does a "rollback" if there was an error (it was not a typo.)
Also BTW: use $this->db->trans_status() === FALSE to check for an error during the transaction.
Update: Here's some (untested) code to do it in a transaction so that the inserts are not seen by others until you are ready:
$failure = array(); //the array where we store what failed
$done = false;
do {
$this->db->trans_begin();
foreach ($data as $key => $ressourceCsv){ //data is an array of arrays to feed the database
$this->ajout_ressource($ressourceCsv); //method to insert (basically, just an insert with active record)
if ($this->db->trans_status() === false) { // an insert failed
$failure[] = $ressourceCsv['title']; // save the failed title
unset($data[$key]); // remove failed insert from data set
$this->db->trans_rollback(); // rollback the transaction
break; // retry the insertion
}
}
$done = true; // completed without failure
} while (count($data) and ! $done); // keep going until no data or success
/*
* Two options (uncomment one):
* 1. Commit the successful inserts even if there were failures.
$this->db->trans_commit();
* 2. Commit the successful inserts only if no failures.
if (count($failure)) {
$this->db->trans_rollback();
} else {
$this->db->trans_commit();
}
*/
return $failure;

Apex batch not executing all records

I'm relatively new to Apex, but I have a question about a batch job that I am creating. I am trying to insert AccountTeamMember records based on my company's territory alignment. The code seems to be working fine, but with one flaw: it is only inserting 100 AccountTeamMember records per user (it should be closer to 400, as that is how many I have loaded in my dev sandbox). Does anyone know what I can do to get an AccountTeamMember record inserted for all accounts per user, rather than 100 of the ~400? Is it something to do with the query including parent-child relationships and governor limits since it is such an even number (100)?
Here is the relevant code:
//list to hold new account teams
List<AccountTeamMember> acctMembers = new List<AccountTeamMember>();
//list to hold new account sharing rules
List<AccountShare> acctSharingRules = new List<AccountShare>();
global Database.querylocator start(Database.BatchableContext BC){
String query = 'SELECT (SELECT User__c FROM Territory_Users__r), (SELECT Account__c FROM Territory_Accounts__r) FROM Territory_Master__c';
return Database.getQueryLocator(query);}
global void execute(Database.BatchableContext BC, List<sObject> scope){
for (sObject s : scope) {
Territory_Master__c tm = (Territory_Master__c) s;
Territory_User__c[] userList = tm.getSObjects('Territory_Users__r');
Territory_Account__c[] accountList = tm.getSObjects('Territory_Accounts__r');
if (userList != null && accountList != null){
for(Territory_User__c uu : userList){
for(Territory_Account__c aa: accountList){
AccountTeamMember addRecord = new AccountTeamMember();
addRecord.AccountId = aa.Account__c;
addRecord.TeamMemberRole = 'Sales Rep';
addRecord.UserId = uu.User__c;
acctMembers.add(addRecord);
AccountShare addSharing = new AccountShare();
addSharing.AccountId = aa.Account__c;
addSharing.OpportunityAccessLevel = 'Read';
addSharing.CaseAccessLevel = 'Read';
addSharing.AccountAccessLevel = 'Edit';
addSharing.UserOrGroupId = uu.User__c;
acctSharingRules.add(addSharing);
}
}
}
}
//DML
if(acctMembers.size() > 0){
insert acctMembers;
}
if(acctSharingRules.size() > 0){
insert acctSharingRules;
}
}
Thanks,
Trey
FYI: This is the final result based on the answer to the question:
global Database.querylocator start(Database.BatchableContext BC){
String query = 'SELECT Id FROM Territory_Master__c';
return Database.getQueryLocator(query);}
global void execute(Database.BatchableContext BC, List<sObject> scope){
for(sObject s : scope){
Territory_Master__c tm = (Territory_Master__c) s;
List<Territory_User__c> userList = [SELECT User__c FROM Territory_User__c WHERE Territory_Master__c = :tm.Id];
List<Territory_Account__c> accountList = [SELECT Account__c FROM Territory_Account__c WHERE Territory_Master__c = :tm.Id];
if (userList != null && accountList != null){
for(Territory_User__c uu : userList){
for(Territory_Account__c aa: accountList){
AccountTeamMember addRecord = new AccountTeamMember();
addRecord.AccountId = aa.Account__c;
addRecord.TeamMemberRole = 'Sales Rep';
addRecord.UserId = uu.User__c;
acctMembers.add(addRecord);
acctSharingRules.add(new AccountShare(
AccountId = aa.Account__c,
OpportunityAccessLevel = 'Read',
CaseAccessLevel = 'Read',
AccountAccessLevel = 'Edit',
UserOrGroupId = uu.User__c)
);
}
}
}
}
//DML
if(acctMembers.size() > 0){
insert acctMembers;
}
if(acctSharingRules.size() > 0){
insert acctSharingRules;
}
}
I suspect that your subqueries got limited and it's your job to make sure you've finished with this record before moving on to the next one.
http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_calls_soql_relationships.htm
Subquery results are like regular query results in that you might need
to use queryMore() to retrieve all the records if there are many
children. For example, if you issue a query on accounts that includes
a subquery, your client application must handle results from the
subquery as well
(the example is for Java calling Salesforce so don't copy-paste the code, try to understand the concept. As far as I know Apex doesn't have this queryMore method, it's for integrations only)
Option 1
Funny enough, it should in theory work if you'd change the for loops to this:
for(Territory_User__c uu : tm.getSObjects('Territory_Users__r')){
for(Territory_Account__c aa: tm.getSObjects('Territory_Accounts__r'))
That's because for loops written in that way should automatically call their internal queryMore().
If it won't work (I haven't tested it), you'll have to make a bit more complex changes.
Option 2
Remove the subqueries from the main query, you'll have to put them in the execute(). Something like this:
for(sObject s : scope){
Territory_Master__c tm = (Territory_Master__c) s;
for(Territory_User__c uu : [SELECT User__c FROM Territory_User__c WHERE Territory_Master__c = :tm.Id]){
for(Territory_Account__c aa: [SELECT Account__c FROM Territory_Account__c WHERE Territory_Master__c = :tm.Id]){
acctMembers.add(new AccountTeamMember(
AccountId = aa.Account__c,
TeamMemberRole = 'Sales Rep',
UserId = uu.User__c)
);
acctSharingRules.add(new AccountShare(
AccountId = aa.Account__c,
OpportunityAccessLevel = 'Read',
CaseAccessLevel = 'Read',
AccountAccessLevel = 'Edit',
UserOrGroupId = uu.User__c)
);
}
}
}
Side notes
Is there a chance you'll hit a limit of 50K rows retrieved across all objects (Territory Master/User/Account? If that's the case you might have to limit the scope of your batch job (optional second parameter passed to the Database.executeBatch()).
This trick can make your script execute bit faster and use less statements (so you won't hit another governor limit):
acctSharingRules.add(new AccountShare(
AccountId = aa.Account__c,
OpportunityAccessLevel = 'Read',
CaseAccessLevel = 'Read',
AccountAccessLevel = 'Edit',
UserOrGroupId = uu.User__c)
);
There's nothing obviously wrong with your code there, so if I were you I'd try to narrow down the cause of the problem. The source of the problem is going to be one of:
Your query is only returning 100 items in accountList or userList or both
The insert can only handle 100 items at a time and is silently failing to insert the rest
The insert is failing for the items after 100
If you haven't already done so, you should familiarize yourself with using Debug Logs in Salesforce.com (Setup | Monitoring | Debug Logs). Turn them on, then run this code with some System.debug calls in there to figure out:
How many items are in userList and accountList?
If it's more than 100, when you go to insert the share rows, are some of those inserts failing? If you use Database.insert() instead of just the insert statement then you'll get a SaveResult[] back that will tell you for each row you attempted to insert whether it was successful or errored out.
So I can't say off the top of my head why this is failing, and I don't see any particular limit that should apply here, but the above should help you debug it at least.

MySQL: Direct Update or Select before a conditional Update?

Just curious to know- if we need to do a conditional Update in a large table, then which is the best approach-
Directly doing an Update or check for existing entry before Update.
function doDirectUpdate()
{
// UPDATE table WHERE condn
}
OR
function doCheckAndUpdate()
{
// SELECT COUNT(id) AS exist FROM table WHERE condn
if(id exists)
{
// UPDATE table WHERE condn
}
else
{
echo 'No matching entry';
}
}
One should not perform both a SELECT, then a conditional UPDATE, simply to display the number of matching rows -- or lack of matching rows. The only time that you should perform the SELECT is if you have other logic that states to not update if there are more than X matching rows.
UPDATE returns the number of rows that were updated, and therefor matched. You should take the return value, from UPDATE, then alert if there are no matching entries.
function doUpdateAndAlertIfNotMatched()
{
numberOfRowsUpdated = UPDATE table WHERE condn;
if(numberOfRowsUpdated == 0)
{
echo 'No matching entry';
}
}
reference: http://dev.mysql.com/doc/refman/5.0/en/update.html#id844302

Resources