If I have the entities Department and Employee, after fetching all the departments, I can see that the employees are still in fault. My goal is to get the set of employees related to a certain departament. From what I read in the documentation, I shouldn't need to fetch that because the fault is fired when it's necessary. I tried the following, but the set is still empty and NSLog shows that the employees are still fault:
Department *dep = (Department *)[self.fetchedDepController objectAtIndexPath:indexPath];
NSSet *employeesSet = [dep employee];
NSLog(#"Department: %#", dep);
What am I missing?
They will be faults until you access a property of the employees. For example:
NSSet *employeesSet = [dep employee];
for (Employee *employee in employeesSet) {
float pay = 0.0;
for (Day *day in employee.timecard) { // relationship fires fault
float hours = (day.clockout - day.clockin) / 60.0 / 60.0;
if (hours > 8.0) pay += employee.payrate * (8.0 + (hours - 8.0) * 1.5); // attribute would too
else pay += employee.payrate * hours;
employee.pay = pay; // so would setting an attribute
Because why do all the examples have to be for salaried employees?
That will fire the faults of all the employees.
But you normally don't need to worry about it, since any object that is a fault is supposed to cease being a fault any time you need it to. But yeah, I don't trust it either. If you have set up awakeFromFetch and didTurnInto fault, then you have the possibility of some dependence on when an object changes its nature. If you do things only like they intend and expect you to, then you have no problem, but the documentation isn't totally clear on what the limits of what's intended, allowed, and expected are.
Related
I have records that have an index attribute to maintain their position in relation to each other.
I have a plugin that performs a renumbering operation on these records when the index is changed or new one created. There are specific rules that apply to items that are at the first and last position in the list.
If a new (or existing changed) item is inserted into the middle (not technically the middle...just somewhere between start and end) of the list a renumbering kicks off to make room for the record.
This renumbering process fires in a new execution pipeline...We are updating record D. When I tell record E to change (to make room for D) that of course fires the plugin on update message.
This renumbering is fine until we reach the end of the list where the plugin then gets into a loop with the first business rule that maintains the first and last record differently.
So I am trying to think of ways to pass a flag to the execution context spawned by the renumbering process so the recursion skips the boundary edge business rules if IsRenumbering == true.
My thoughts / ideas:
I have thought of using the Depth check > 1 but that isn't a reliable value as I can't explicitly turn it on or off....it may happen to work but that is not engineering a solid solution that is hoping nothing goes bump. Further a colleague far more knowledgeable than I said that when a workflow calls a plugin the depth value is off and can't be trusted.
All my variables are scoped at the execute level so as to avoid variable pollution at the class level....However if I had a dictionary object, tuple, something at the class level and one value would be the thread id and the other the flag value then perhaps my subsequent execution context could check if the same owning thread id had any values entered.
Any thoughts or other ideas on how to pass context information to a new pipeline would be greatly appreciated.
Per Nicknow sugestion I tried sharedvariables but they seem to be going out of scope...:
First time firing post op:
if (base.Stage == EXrmPluginStepStage.PostOperation)
{
...snip...
foreach (var item in RenumberSet)
{
Context.ParentContext.SharedVariables[recordrenumbering] = "googly";
Entity renumrec = new Entity("abcd") { Id = item.Id };
#region We either add or subtract indexes based upon sortdir
...snip...
renumrec["abc_indexfield"] = TmpIdx + 1;
break;
.....snip.....
#endregion
OrganizationService.Update(renumrec);
}
}
Now we come into Pre-Op of the recursion process kicked off by the above post-op OrganizationService.Update(renumrec); and it seems based upon this check the sharedvariable didn't carry over...???
if (!Context.SharedVariables.Contains(recordrenumbering))
{
//Trace.Trace("Null Set");
//Context.SharedVariables[recordrenumbering] = IsRenumbering;
Context.SharedVariables[recordrenumbering] = "Null Set";
}
throw invalidpluginexception reveals:
Sanity Checks:
Depth : 2
Entity: ...
Message: Update
Stage: PreOperation [20]
User: 065507fe-86df-e311-95fe-00155d050605
Initiating User: 065507fe-86df-e311-95fe-00155d050605
ContextEntityName: ....
ContextParentEntityName: ....
....
IsRenumbering: Null Set
What are you looking for is IExecutionContext.SharedVariables. Whatever you add here is available throughout the entire transaction. Since you'll have child pipelines you'll want to look at the ParentContext for the value. This can all get a little tricky, so be sure to do a lot of testing - I've run into many issues with SharedVariables and looping operations in Dynamics CRM.
Here is some sample (very untested) code to get you started.
public static bool GetIsRenumbering(IPluginExecutionContext pluginContext)
{
var keyName = "IsRenumbering";
var ctx = pluginContext;
while (ctx != null)
{
if (ctx.SharedVariables.Contains(keyName))
{
return (bool)ctx.SharedVariables[keyName];
}
else ctx = ctx.ParentContext;
}
return false;
}
public static void SetIsRenumbering(IPluginExecutionContext pluginContext)
{
var keyName = "IsRenumbering";
var ctx = pluginContext;
ctx.SharedVariables.Add(keyName, true);
}
A very simple solution: add a bit field to the entity called "DisableIndexRecalculation." When your first plugin runs, make sure to set that field to true for all of your updates. In the same plugin, check to see if "DisableIndexRecalculation" is set to true: if so, set it to null (by removing it from the TargetEntity entirely) and stop executing the plugin. If it is null, do your index recalculation.
Because you are immediately removing the field from the TargetEntity if it is true the value will never be persisted to the database so there will be no performance penalty.
How do I sort my fetched results by a value that is returned by a method in a category of the entity I'm fetching?
In my category, I sum up several values from the entity's to-many relationship, then divide by the number of objects in the relationship, effectively creating an average that I return in my category method as a float value.
Here is my code:
In the Category.h
- (float)smallPenaltyAvg;
In the Category.m
- (float)smallPenaltyAvg{
float smallPenaltyAvg = 0;
for (Match *mtch in self.matches) {
smallPenaltyAvg += [mtch.penaltySmall floatValue];
}
if ([self.matches count] > 0) {
smallPenaltyAvg = (float)smallPenaltyAvg/(float)[self.matches count];
}
return smallPenaltyAvg;
}
And when I call it in the Core Data Table View Controller class that I created...
NSFetchRequest *poolRequest = [[NSFetchRequest alloc] initWithEntityName:#"Team"];
poolRequest.predicate = [NSPredicate predicateWithFormat:#"regionalIn.name = %#", _regionalToDisplay];
poolRequest.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"smallPenaltyAvg" ascending:YES]];
And I have the Category.h file imported on every file previously mentioned outside of the Category.h file itself.
It gives me the error of:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'keypath smallPenaltyAvg not found in entity <NSSQLEntity Team id=5>
Am I not allowed to do this?
If I am, what am I doing wrong?
I do not think this has anything to do with the kind of persistent store.
The trick is to create an appropriate attribute in the managed object model, and mark it as Transient. Then override the getter of this attribute to do your calculations.
Now your fetch request should work as expected (although there are some caveats with fetched results controllers).
As for the SQLite problem, when you add the SQLite store with
- (NSPersistentStore *)addPersistentStoreWithType:(NSString *)storeType
configuration:(NSString *)configuration
URL:(NSURL *)storeURL
options:(NSDictionary *)options
error:(NSError **)error
just pass NSSQLiteStoreType as the storeType. The other options are binary and in-memory, so in this sense this is indeed the "default".
This is not possible when using a backing SQLite store.
My suggestion is you persist the average property, and maintain it yourself by overriding the Match setCategory: property and making the calculation there for every match added.
What I did to solve my problem was create a new attribute for every average or sum that I needed in the Team object from all of its Match objects' attributes and then created a method in the TeamCategory file that populated those averages and that method was called every time a Match object was inserted into the Team object. It took a while to do, but it works now. If there is a better solution, I'm still open for suggestions.
I have a large collection of documents and each is valid for a range of days. The range could be from 1 week up to 1 year. I want to be able to get all the documents that are valid on a specific day.
How would I do that?
As an example say I have the following two documents:
doc1 = {
// 1 year ago to today
start_at: "2012-03-22T00:00:00Z",
end_at: "2013-03-22T00:00:00Z"
}
doc2 = {
// 2 months ago to today
start_at: "2012-01-22T00:00:00Z",
end_at: "2013-03-22T00:00:00Z"
}
And a map function:
(doc) ->
emit([doc.start_at, doc.end_at], null)
So for a date of 6 months ago I would only get doc1, a date of 1 week ago I would get both documents, and with a date of tomorrow I would receive no documents.
Note that actual resolution needs to be down to the second of the request being made and there are lots of documents, so strategies of emitting a key for every valid second would not be appropriate.
You could call emit for each day in your range, and then you can easily pick out the documents available for a specific day.
function(doc) {
var day = new Date(doc.start),
end = new Date(doc.end).getTime();
do {
emit(day);
day = new Date(day.getFullYear(), day.getMonth(), day.getDate() + 1);
} while (day.getTime() <= end);
}
Even though you will have lots of documents, if you leave out the value part (2nd param) of your emit, the index will be as small as it could possibly be.
If you need to get more sophisticated, you could try out couchdb-lucene. You can index date fields as date objects and execute range queries with multiple fields in 1 request.
You can translate the problem into the computational geometry problem of location. For documents in two dimensional plane [x,y]=[start_at,end_at] query for those, which are valid at date date is the list of the points in the rectangle bounded by: left=-infinity, right=date (start_at<date) and bottom=date, top=infinity (end_at>date).
Unfortunately, CouchDB team underrate the power of computational geometry and does not support multidimensional queries. There is GeoCouch extension that allows you to do this kind of queries as easy as:
http://localhost:5984/places/_design/main/_spatial/points?bbox=0,0,180,90
on the view emitting spatial value:
emit({ type: "Point", coordinates: [doc.start_at, doc.end_at] }, doc);
The problem is different data type. You get float in range of [-180.0,180.0]/[-90.0,90.0] and need at least int (UNIX time format). If GeoCouch works for you in ranges bigger then 180.0 and the precision of float operation designed for geographical calculation is sufficient for dates with precision of seconds your problem is solved :) I am sure, with few tricks and hacks, you could solve this problem efficiently in geo software. If not GeoCouch then perhaps ElastiSearch (also support multidimensional queries) which is easy to use with CouchDB with its River plugins system.
Well, my problem is the follow. I create a entity. i call this new_logpuntossegmentacin that has a relation 1 to ∞ with account, when i put in the registration plugin message, create i hope that the follow code fill out the field puntosacumulados but nothing happens:
cli is a Account from a List
total is a Decimal
total = total1 + total2 + total3 + total4 + total5 + total6;
cli.new_puntosacumulados.Insert(i, total.ToString());
svcContext.UpdateObject(cli);
svcContext.SaveChanges();
i++;
if (!String.IsNullOrEmpty(total.ToString()))
{
tracingService.Trace("Response = {0}", total.ToString());
}
tracingService.Trace("Done.");
A couple of questions which may give a little more context:-
1) When you say nothing happens, do you mean that the value is not updated in the database or it doesn't appear updated on the form? If the latter then it may be when the plug in is firing (pre vs post).
2) Could you perhaps post the rest of the method as it may be useful to get some context on some of the other parameters, e.g. what is "i" iterating over here?
Thanks
For a future project we have been assigned to create a simple concept (inside Magento) that would has to do the following:
A customer has the ability to choose between different shipping methods, one of them being "Ship2Shop", which sends the product to a physical store of choice and the customer has to go an pick it up.
When a customer selects this "ship2shop" shipping method, a certain percentage (eg: 25%) of the total amount has to be paid online (via a pre-defined payment method) and the remaining 75% has to be paid in the physical store when the customer goes and pick up the products he ordered.
How would you go about this?
Idea that we were having is modify the checkout/order session and modify the "grand total" amount (saving the original in a session ofcourse). When the customer is then sent to the external payment processor the "modified grand total" is sent along. Once the customer returns on the magento platform we would modify the order by restoring the original grand total the way it was and updating the total paid and total due amount.
Anyone got any other ideas about this?
EDIT:
After feedback from Anton S below I managed to add an "advance payment total". However Im still having a problem
In the config.xml I have added the following in the tag:
acsystems_advancepayment/total_custom
grand_total
I want my advance payment to show AFTER the grand total, for some reason, magento won't do that...
EDIT2: Collect method
public function collect(Mage_Sales_Model_Quote_Address $address)
{
parent::collect($address);
$quote = $address->getQuote();
$advancePaymentAmount = 0;
$baseAdvancePaymentAmount = 0;
$items = $address->getAllItems();
if (!count($items)) {
$address->setAdvancePaymentAmount($advancePaymentAmount);
$address->setBaseAdvancePaymentAmount($baseAdvancePaymentAmount);
return $this;
}
$address->setBaseAdvancePayment($address->getGrandTotal()*(0.25));
$address->setAdvancePayment($address->getGrandTotal()*(0.25));
$address->setAdvancePaymentAmount($address->getGrandTotal()*(0.25));
$address->setBaseAdvancePaymentAmount($address->getGrandTotal()*(0.25));
$address->setGrandTotal($address->getGrandTotal() - $address->getAdvancePaymentAmount());
$address->setBaseGrandTotal($address->getBaseGrandTotal()-$address->getBaseAdvancePaymentAmount());
return $this;
}
refer to this thread where adding total objects is explained Magento: adding duties/taxes to a quote during review
Basically you should add your own total object based on your shipping method selection, then it will also be shown in totals as separate row and you can show this in every e-mail or place where totals are exposed
public function collect(Mage_Sales_Model_Quote_Address $address)
{
//this is for the loop that you are in when totals are collected
parent::collect($address);
$quote = $address->getQuote();
//variables for your own object context
$advancePaymentAmount = 0;
$baseAdvancePaymentAmount = 0;
$items = $address->getAllItems();
if (!count($items)) {
$address->setAdvancePaymentAmount($advancePaymentAmount);
$address->setBaseAdvancePaymentAmount($baseAdvancePaymentAmount);
return $this;
}
//calculated based on other total object and don't edit other totals inside your own as your calculations would be always false and so would be next total object in the cycle and so on
$baseAdvancePaymentAmount = $address->getBaseGrandTotal()*(0.25);
$advancePaymentAmount = $address->getQuote()->getStore()->convertPrice($baseAdvancePaymentAmount, false);
//this is just for your own object context
$address->setBaseAdvancePaymentAmount($baseAdvancePaymentAmount);
$address->setAdvancePaymentAmount($advancePaymentAmount);
/*
* this is for the loop that you are in when totals are collected and
* those are set to 0 for each totals collecting cycle
*/
$this->_setBaseAmount($baseAdvancePaymentAmount);
$this->_setAmount($advancePaymentAmount);
return $this;
}
Another option is to change the "grand_total" in your payment module, that way the sessions aren't altered..