Salesforce Asked by Luís Aguiar on December 10, 2021
We have the need to insert price ranges (between Quantidade Quantidade_Cx_Minima__c and_Cx_Maxima__c) dynamically (in the object Escalao_de_Promocao__c), for specific products in specific stores, so that they handle promotions in stores. The quantities of a product relates to the discount on unit price. The type of discount accepted are configured in a related product custom object.
The manager inserts ranges integers like 0-9, unit price or discount percentage (there is a validating rule demanding one of the fields filled) = 20€ or -1%;
so 10 to 19 = 18€ or -3%; etc.
These ranges can be sequential, or if there is a gap, or a range is deleted, it can be filled later. Like:
with these ranges: 10-19, 30-39, 40-49 these inserts are valid: 0-9, 20-25, 50-59
and these should be invalid: 0-20, 20-25, 20-60
the validation is now made with a trigger a few validation rules:
the trigger:
public with sharing class tl_Escalao_de_Promocao {
private th_Escalao_de_Promocao handler;
public tl_Escalao_de_Promocao(th_Escalao_de_Promocao handler) {
this.handler = handler;
}
public void ValidateEscaloesPromocoes(List<Escalao_de_Promocao__c> l_EscalaoPromocao) {
List<String> sIDPromocao = new List<String>();
List<String> sIDProduto = new List<String>();
for(Escalao_de_Promocao__c ep: l_EscalaoPromocao){
sIDPromocao.add(ep.Promocao__c);
sIDProduto.add(ep.Produto_da_Promocao__c);
}
if(sIDPromocao.size()>0 || sIDProduto.size()>0){
Map<Decimal, Decimal> Map_Number = new Map<Decimal, Decimal>();
List<Escalao_de_Promocao__c> escaloes = [
SELECT Id, Quantidade_Cx_Minima__c, Quantidade_Cx_Maxima__c
FROM Escalao_de_Promocao__c
WHERE Promocao__c IN: sIDPromocao and Produto_da_Promocao__c IN: sIDProduto ];
for(Escalao_de_Promocao__c e: escaloes){
for(Decimal i= e.Quantidade_Cx_Minima__c; i <= e.Quantidade_Cx_Maxima__c; i++ ){
Map_Number.put(i, i);
}
}
system.debug('Map_Number: ' + Map_Number);
for(Decimal valores: Map_Number.keySet()){
for(Escalao_de_Promocao__c esc : l_EscalaoPromocao ){
if(Map_Number.keySet().contains(esc.Quantidade_Cx_Minima__c) ||
Map_Number.keySet().contains(esc.Quantidade_Cx_Maxima__c)){
if(!Test.isRunningTest()) esc.addError('ERRO');
}
}
}
}
}
The process here is:
so far, gives an interface error with inserting:
with validation rules we validate maximum bellow minimum.
But we cant validate if a range starts and ends in empty slots, while overlaping a filled range. So inserting this should also be invalid: 8-29. And here is the remaining issue.
Does anyone have a better solution? Thanks
edit: my implementation
public void ValidateEscaloesPromocoes(List<Escalao_de_Promocao__c> l_EscalaoPromocao) {
List<String> sIDPromocao = new List<String>();
List<String> sIDProduto = new List<String>();
Set<Decimal> allocatedRanges = new Set<Decimal>();
for(Escalao_de_Promocao__c ep: l_EscalaoPromocao){
sIDPromocao.add(ep.Promocao__c);
sIDProduto.add(ep.Produto_da_Promocao__c);
}
if(sIDPromocao.size()>0 || sIDProduto.size()>0){
Set<Decimal> currentRange = new Set<Decimal>();
List<Escalao_de_Promocao__c> escaloes = [SELECT Id, Quantidade_Cx_Minima__c, Quantidade_Cx_Maxima__c FROM Escalao_de_Promocao__c WHERE Promocao__c IN: sIDPromocao and Produto_da_Promocao__c IN: sIDProduto ];
System.debug('escaloes: ' + escaloes);
for(Escalao_de_Promocao__c rec : escaloes){
for(Decimal i = rec.Quantidade_Cx_Minima__c; i <= rec.Quantidade_Cx_Maxima__c; i++){
currentRange.add(i);
}
}
System.debug('allocatedRanges: ' + allocatedRanges);
if(!currentRange.clone().removeAll(allocatedRanges)){
allocatedRanges.addAll(currentRange);
System.debug('currentRange s overlap: ' + currentRange);
} else {
System.debug('allocatedRanges overlap: ' + allocatedRanges);
if(allocatedRanges.containsAll(currentRange)){
System.debug('currentRange contains: ' + currentRange);
} else {
System.debug('currentRange not contains: ' + currentRange);
}
}
System.debug('allocatedRanges: ' + allocatedRanges);
}
}
I think that what you have is fairly close.
While there are some datasets that have nice properties that can help us design an algorithm (IP Address allocation maps very nicely to a binary tree, for example), I don't think that this problem really gives us much to work with.
About the only suggestion that I can come up with is I don't think using a Map is appropriate here. Instead, I'd use a simple Set<Decimal>
.
The idea is to turn each range into a Set<Decimal>
(i.e. Minimum = 9, Maximum = 16 becomes Set<Decimal>{9, 10, 11, 12, 13, 14, 15, 16}
), and then use a combination of removeAll()
, retainAll()
, and containsAll()
to check for overlap.
// I'm just assuming that there's no issue with combining the existing and new records
// That assumption means that existing records are re-subjected to the overlap check
// We need to generate ranges for them (the existing records) anyway, and I don't think
// that the Set class methods consume too much cpu time
Set<Decimal> allocatedRanges = new Set<Decimal>();
for(Record__c rec :allRecords){
Set<Decimal> currentRange = new Set<Decimal>();
for(Decimal i = rec.min; i <= rec.max; i++){
currentRange.add(i);
}
// removeAll returns a boolean
// false = original set was not changed
// true = original set did undergo a change
// The extra clone() call in there helps ensure that currentRange is not modified
// (so that we can use it to help determine what type of overlap we have)
if(!currentRange.clone().removeAll(allocatedRanges)){
// In here, we know that there is no overlap between currentRange
// and allocatedRanges
// Add current to allocated so we can check the next record
allocatedRanges.addAll(currentRange);
}else{
// Inside this else block, we know that there was _some_ overlap
// Determining if that's due to partial overlap, a repeated range,
// or a sub-range requires extra work
if(allocatedRanges.containsAll(current)){
// In here, we know that we either repeated a range, or are a
// sub-range
// Figuring out which it is, though, may not be possible without
// additional data
}else{
// In here, we know that there is some non-complete overlap
// i.e. not all of the range has already been allocated
}
}
}
A pseudo-code description of the above (which may be more readable) is
declare a primary set for things that have already been allocated
for each Escalao_de_Promocao__c record{
declare a second set to hold the range for _this_ particular record
iterate from record min to record max (to build our range){
add this number to our secondary set
}
does our secondary set contain any numbers from the primary set?{
no, it does not, there is no overlap
add our secondary set into the primary set
continue on to checking the next record
}{
yes, it does, so there is overlap
error out
}
}
It's important that you keep checking for overlap and adding to the allocated set for each individual record.
There may be a way to do this without needing to expand a range into a Set, but unless you're running into the CPU or Heap governor limits I don't think it's worth trying to come up with something more clever than this.
Answered by Derek F on December 10, 2021
Get help from others!
Recent Questions
Recent Answers
© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP