I am currently working on a lightning quick action which will update OrderItems on demand from a specified quote. If an OrderItem has no corresponding QuoteLineItem, the line item will be deleted. This is where something strange happens.
Previously, we were not referencing the QuoteLineItemId field when generating OrderItems. I have just recently written code which now references a QuoteLineItemId for every OrderItem. Curiously, when I attempt to sync old OrderItems (in a partial sandbox) I receive the following error.
ResyncOrderLinesfromQuote.AuraHandledException: Something went wrong while deleting orderItems. Error Message: Delete failed. First exception on row 0 with id 8021J000005bcVCQAY; first error: INSUFFICIENT_ACCESS_OR_READONLY, insufficient access rights on object id: []
I do not receive this error when attempting to sync Order Items generated with new code. I suspect the error may have something to do with the fact that the QuoteLineItemId is not referenced on the old OrderItems. but I am at my wits end trying to identify what exactly is causing the problem and how to fix it. I have scoured the internet for answers but haven't found anything yet.
Note: I have full Admin/Modify All Data rights to all involved objects.
I am a new, self taught programmer. Any constructive criticism is greatly appreciated. Thanks!
Class
public without sharing class ResyncOrderLinesfromQuote {
//Purpose: From Button on Order record page, sync's QuoteLineItems>OrderItems one way. Deletes OrderItems without corresponding QuoteLineItems in batch function.
@AuraEnabled
public static Void syncLineItems(Id orderId) {
Order ord = [SELECT Id, QuoteId FROM Order WHERE Order.Id =: orderId]; //query order here with orderId
Map<ID, OrderItem> orderItemMap = getOrderItems(ord.Id);
//System.debug('orderItemMap: ' + orderItemMap);
//Two main maps below
Map<ID, QuoteLineItem> quoteLineItemMap = getQuoteLineItemMap(ord.QuoteId);
//System.debug('quoteLineItemMap: ' + quoteLineItemMap);
///The keys are the QuoteLineItemId fields of (value:) OrderItems. Nulls are ommitted.
Map<ID, OrderItem> qliIdsToOrderItemMap = new Map<Id, OrderItem>();
///OrderItemIds for orders which have null for the QuoteLineItemId field
Set<Id> orphanOIs = new Set<Id>();
for(OrderItem line : orderItemMap.values()) {
if(line.QuoteLineItemId != null) {
System.debug('line.QuoteLineItemId != null');
qliIdsToOrderItemMap.put(line.QuoteLineItemId, line);
} else {
orphanOIs.add(line.Id);
}
}
System.debug('quoteLineItemMap: ' + quoteLineItemMap);
System.debug('qliIdsToOrderItemMap: ' + qliIdsToOrderItemMap);
System.debug('orphanOIs: ' + orphanOIs);
Set<Id> orphanQuoteIds = new Set<Id>(quoteLineItemMap.keySet());
orphanQuoteIds.removeAll(qliIdsToOrderItemMap.keySet());
System.debug('quoteLineItemMap2: ' + quoteLineItemMap);
List<OrderItem> orderItemsToUpdate = processOrderItemsForUpdates(quoteLineItemMap, qliIdsToOrderItemMap);
List<OrderItem> orderItemsToInsert = createOrderItemsForOrphanQuoteLineItems(quoteLineItemMap, orphanQuoteIds, orderId);
List<OrderItem> orderItemsToDelete = [SELECT id FROM OrderItem WHERE Id IN :orphanOIs];
System.debug('orderItemsToDelete: ' + orderItemsToDelete);
try {
delete orderItemsToDelete;
} catch (DmlException e) {
System.debug(e);
throw new AuraHandledException('Something went wrong while deleting orderItems. Error Message: ' + e.getMessage());
}
//orderItemsToUpdate.addAll(orderItemsToInsert);
update orderItemsToUpdate;
system.debug('orderItemsToInsert: ' + orderItemsToInsert);
system.debug('orderItemsToUpdate: ' + orderItemsToUpdate);
// try {
// insert orderItemsToInsert;
// } catch (DmlException e) {
// System.debug(e.getMessage());
// }
Database.SaveResult[] srList = Database.insert(orderItemsToInsert, false);
// Iterate through each returned result
for (Database.SaveResult sr : srList) {
if (!sr.isSuccess()) {
// Operation failed, so get all errors
for(Database.Error err : sr.getErrors()) {
System.debug('The following error has occurred.');
System.debug(err.getStatusCode() + ': ' + err.getMessage());
System.debug('Fields that affected this error: ' + err.getFields());
}
}
}
}
public static List<OrderItem> processOrderItemsForUpdates(Map<Id, QuoteLineItem> qliMap, Map<Id, OrderItem> qliIdsToOrderItemMap) {
System.debug('processOrderItemsForUpdates: qliMap - ' + qliMap);
System.debug('processOrderItemsForUpdates: qliIdsToOrderItemMap - ' + qliIdsToOrderItemMap);
List<OrderItem> orderItemsToUpdate = new List<OrderItem>();
///QuoteLineItemIds for QuoteLineItems paired/referenced with/by OrderItems.
Set<ID> matchedQuoteLineItemIDs = new Set<Id>(qliIdsToOrderItemMap.keySet());
/// qliRefInOIs ∩ quoteLineItemMap.keySet()
matchedQuoteLineItemIDs.retainAll(qliMap.keySet());
Boolean recheckOrderStarDate = false;
Date newOrderStartDate = Date.today();
Id currentOrderId = null;
for (Id qliId : matchedQuoteLineItemIDs) {
QuoteLineItem qli = qliMap.get(qliId);
OrderItem oi = qliIdsToOrderItemMap.get(qliId);
System.debug(qli.LastModifiedDate);
System.debug(oi.LastModifiedDate);
//do stuff: copy changes from qli to oi
if(oi.Discount__c != qli.Discount) {
oi.Discount__c = qli.Discount;
}
if(oi.Distributor__c != qli.Distributor__c) {
oi.Distributor__c = qli.Distributor__c;
}
if(oi.NSP_Price__c != qli.NSP_Price__c) {
oi.NSP_Price__c = qli.NSP_Price__c;
}
if(qli.Quote.Override_Product_Description__c == true) {
System.debug('Override_Product_Description__c: ' + qli.Quote.Override_Product_Description__c);
String qlidesc = RecordGenerator.formatLineItemDescription(qli.Description);
system.debug('qlidesc: ' + qlidesc);
system.debug('oi.Description: ' + oi.Description);
if(oi.Description != qlidesc) {
oi.Description = qlidesc;
}
} else {
String qlidesc = RecordGenerator.formatLineItemDescription(qli.Product2.Description);
system.debug('qlidesc: ' + qlidesc);
system.debug('oi.Description: ' + oi.Description);
if(oi.Description != qlidesc) {
oi.Description = qlidesc;
}
}
if(oi.Quantity != qli.Quantity) {
oi.Quantity = qli.Quantity;
}
if(oi.Serial_Number__c != qli.Serial_Number__c) {
oi.Serial_Number__c = qli.Serial_Number__c;
}
//if recheckOrderStarDate == true - will trip an update of the order StartDate upon completion of the list iteration
if(oi.ServiceDate != qli.Start_Date__c){
if(oi.Order.EffectiveDate > qli.Start_Date__c) {
if (recheckOrderStarDate == true) {
if(qli.Start_Date__c < newOrderStartDate) {
newOrderStartDate = qli.Start_Date__c;
}
} else {
recheckOrderStarDate = true;
newOrderStartDate = qli.Start_Date__c;
currentOrderId = oi.OrderId;
}
}
oi.ServiceDate = qli.Start_Date__c;
}
if(oi.EndDate != qli.End_Date__c) {
oi.EndDate = qli.End_Date__c;
}
if(oi.UnitPrice != qli.UnitPrice) {
oi.UnitPrice = qli.UnitPrice;
}
if(oi.PricebookEntryId != qli.PricebookEntryId) {
oi.PricebookEntryId = qli.PricebookEntryId;
}
orderItemsToUpdate.add(oi);
System.debug(qli.LastModifiedDate);
System.debug(oi.LastModifiedDate);
}
if (recheckOrderStarDate == true) {
Order currentOrder = [SELECT Id, EffectiveDate FROM Order WHERE Id =: currentOrderId][0];
currentOrder.EffectiveDate = newOrderStartDate;
update currentOrder;
}
return orderItemsToUpdate;
}
public static List<OrderItem> createOrderItemsForOrphanQuoteLineItems(Map<Id, QuoteLineItem> qliMap, Set<Id> orphanQuoteIds, Id orderId) {
List<OrderItem> newItems = new List<OrderItem>();
for (Id quoteLineItemId : orphanQuoteIds) {
QuoteLineItem quoteLineItemToCopy = qliMap.get(quoteLineItemId);
System.debug('quoteLineItemToCopy: ' + quoteLineItemToCopy);
OrderItem newMatchingOrderItem = RecordGenerator.generateOrderItemFromQuoteLineItem(quoteLineItemToCopy);
newMatchingOrderItem.orderId = orderId;
newItems.add(newMatchingOrderItem);
}
return newItems;
}
public static Map<ID, QuoteLineItem> getQuoteLineItemMap(Id syncedQuoteId) {
Map<ID, QuoteLineItem> quoteLineItemMap = new Map<Id, QuoteLineItem>([SELECT Id,
Discount,
Distributor__c,
NSP_Price__c,
PricebookEntry.ProductCode,
Description,
Product2.Description,
Quantity,
Serial_Number__c,
Start_Date__c,
End_Date__c,
UnitPrice,
PricebookEntryId,
LastModifiedDate,
Quote.Override_Product_Description__C
FROM QuoteLineItem WHERE QuoteLineItem.QuoteId =: syncedQuoteId]);
return quoteLineItemMap;
}
public static Map<ID, OrderItem> getOrderItems(Id currentOrderId) {
Map<ID, OrderItem> orderItemMap = new Map<ID, OrderItem>([SELECT OrderId,
Discount__c,
Distributor__c,
NSP_Price__c,
Description,
Quantity,
Serial_Number__c,
ServiceDate,
EndDate,
UnitPrice,
PricebookEntryId,
QuoteLineItemId,
LastModifiedDate,
Order.EffectiveDate
FROM OrderItem WHERE OrderItem.OrderId =: currentOrderId]);
return orderItemMap;
}
public class AuraHandledException extends Exception {}
}
Code to generate OrderItems
global without sharing class RecordGenerator {
@TestVisible
public static OrderItem generateOrderItemFromQuoteLineItem(QuoteLineItem lineItem) {
OrderItem newOrderItem = new OrderItem();
newOrderItem.Discount__c = lineItem.Discount;
newOrderItem.Distributor__c = lineItem.Distributor__c;
newOrderItem.EndDate = lineItem.End_Date__c;
newOrderItem.NSP_Price__c = lineItem.NSP_Price__c;
newOrderItem.Product2Id = lineItem.Product2Id;
if(lineItem.Quote.Override_Product_Description__c == true) {
newOrderItem.Description = formatLineItemDescription(lineItem.Description);
} else {
newOrderItem.Description = formatLineItemDescription(lineItem.Product2.Description);
}
newOrderItem.Quantity = lineItem.Quantity;
newOrderItem.Serial_Number__c = lineItem.Serial_Number__c;
newOrderItem.ServiceDate = lineItem.Start_Date__c;
newOrderItem.UnitPrice = lineItem.UnitPrice;
newOrderItem.PricebookEntryId = lineItem.PricebookEntryId;
newOrderItem.QuoteLineItemId = lineItem.Id;
return newOrderItem;
}
public static String formatLineItemDescription(String productDescription) {
if (productDescription != null) {
if (productDescription.length() < 255) {
return productDescription;
} else if (productDescription.length() >= 255) {
String formatedDescription = productDescription.substring(0, Math.min(productDescription.length(), 254));
return formatedDescription;
}
return '';
} else {
return null;
}
}
}