I'am designing a new invoicing application. There are a number of features that I don't know how to implement in Core Data. I ask you for help with the following.
To keep things simple assume that there are 2 entities, Invoice entity and Detail entity with to-many relationship 'invoiceDetails' and to-one relationship 'detailInvoice'. Here are my questions.
Detail entity should have attribute 'sequenceNumber' which should be auto-generated when the user adds new detail. For each invoice the sequenceNumber should start at 1 and be incremented as the user adds new details. The sequenceNumber should be used to sort details within their invoice.
Detail entity has also attributes 'numberOfItems' and 'price'. It also should have attribute 'amount' which should be auto-generated as product of numberOfItems and price.
Invoice entity should have attribute 'netAmount' which should be generated as the sum of all detail amounts.
Invoice entity should have attribute 'vat' which should be auto-generated as an expression from netAmount.
Invoice entity should also have attribute 'totalAmount' auto-generated as a sum of netAmount and vat.
Invoice entity should have attribute 'dueTo' auto-generated from current date plus some number of days.
How do I accomplish this in Core Data application? Thanks.
/Mikael
Detail entity should have attribute 'sequenceNumber' which should be auto-generated when the user adds new detail.
You'll have to assign this value yourself. What I'd do is store the highest sequence number as metadata on the persistent store file (see NSPersistentStoreCoordinator's metadata-related methods). Any time you create a new instance, read the highest number from the store metadata, increment it, use that value on the new instance, and then save the new value back to store metadat.
2-6. Calculated attributes
These are generally handled by subclassing NSManagedObject and then overriding the setter methods on attributes whose value affects other attributes. For example, based on #2, the setter for your price attribute would look something like:
- (void)setPrice:(NSDecimalNumber *)price
{
[self willChangeValueForKey:#"price"];
[self setPrimitiveValue:price forKey:price];
[self didChangeValueForKey:#"price"];
// Now calculate the new value for "amount" and set it on self.
}
Follow the same pattern for each case. You can also use key-value observing to watch for changes on these attributes, but I find custom accessors to be clearer and less error-prone.
Related
Can you explain what are "retirable" entities in guidewire? How can we create these entities?
Also explain what is the difference between effdated entities and retirable entities?
Retirable entity is an extension of Editable entity. It has Retired field (boolean field) to do a logic delete.
EffDated entity also is an extension of Editable entity. It has EffectiveDate (Start date) and ExpirationDate (End date).
There are several differences but maybe the most interest for you is that effdated entity is used for entities that you are interest in know whole history (by example PolicyPeriod), then when you "deleted" an effdated item a new item is created with ExpirationDate modified to do a logic deleted.
I have added two custom fields in the Screens: Payment and Application(AR302000), Cash Transactions(CA304000), Funds Transfers(301000) and Back Deposits(305000), and I pretend to add those fields also in the Table CATran
I have a Generic Inquiry that mimic the Report Cash Account Detail (CA633500) where I can see that the Field ExtReference and Description are copied from the Original Ducument (AR302000,CA304000,etc) to the CATran records.
If I would like to pass the value of my custom field to the CATran Table, to get the same behavior that the External Reference and Description does.
Does anybody know what is the action'name to override the Insertion of CATran Detail?
Form AR302000 uses following class: PX.Objects.AP.APPaymentEntry. It means that you should create extension for this class in way similar to this:
public class APPaymentEntryExt : PXGraphExtension<APPaymentEntryExt>
{
}
Then you can use extending with help of CATran_RowInserting for initial values configuration and CATran_RowInserted for modification of inserted values
I've added three custom decimal? field on Requisition master (RQ302000) and need to prorate the total value of those three fields to the each Requisition line using line base total cost/master total cost ratio and displayed the result as Additional Cost (also custom decimal? field) of each line.
This calculation should be triggered when those three new field are updated.
What I don't understand are:
1. What events should be modify related to this needs
2. If it is event on master field, how to get value from line extension field
3. If it is event on line field, how to get value from master extension field
Requisition Custom Screen
1. "What events should be modify related to this needs?"
In a scenario, like yours, one should use combination of RowInserted, RowUpdated and RowDeleted handlers:
The RowInserted event handler is used to implement the business logic for:
Inserting the detail data records in a one-to-many relationship.
Updating the master data record in a many-to-one relationship.
Inserting or updating the related data record in a one-to-one relationship.
The RowUpdated event handler is used to implement the business logic of:
Updating the master data record in a many-to-one relationship.
Inserting or updating the detail data records in a one-to-many relationship.
Updating the related data record in a one-to-one relationship.
The RowDeleted event handler is used to implement the business logic of:
Deleting the detail data records in a one-to-many relationship.
Updating the master data record in a many-to-one relationship.
Deleting or updating the related data record in a one-to-one relationship.
Also FieldUpdated handlers be considered for your scenario:
The FieldUpdated event handler is used to implement the business logic associated with changes to the value of the DAC field in the following cases:
Assigning the related fields of the data record containing the modified field their default values or updating them
Updating any of the following:
The detail data records in a one-to-many relationship
The related data records in a one-to-one relationship
The master data records in a many-to-one relationship
Refer to API Reference in Help -> Acumatica Framework -> API Reference -> Event Model and T200 developer class for additional information and examples on Acumatica Framework event model.
2. "If it is event on master field, how to get value from line extension field?"
In Acumatica custom fields are declared via DAC extensions. To access the DAC extension object, you can use the following methods:
The GetExtension() generic method available for each DAC instance:
ContactExt contactExt = curLead.GetExtension<ContactExt>();
The GetExtension(object) generic method declared within the non-generic PXCache class
ContactExt contactExt = Base.LeadCurrent.Cache.GetExtension<ContactExt>(curLead);
or
ContactExt contactExt = Base.Caches[typeof(Contact)].GetExtension<ContactExt>(curLead);
The GetExtension(object) static generic method of the PXCache generic class
ContactExt contactExt = PXCache<Contact>.GetExtension<ContactExt>(curLead);
To get value from line extension field, you should first select records from the Lines data view, then use one of the methods described above to access instance of DAC extension class, for instance:
foreach(RQRequisitionLine line in Base.Lines.Select())
{
RQRequisitionLineExt lineExt = line.GetExtension<RQRequisitionLineExt>();
}
3. "If it is event on line field, how to get value from master extension field"
That's an easy one: same 3 approaches described above, this time applied to Current property of the primary Document data view, for instance:
Base.Document.Current.GetExtension<RQRequisitionExt>();
My NSFetchedResultsController work great, as long as only "basic" attributes get changed. However if I have a label which is calculated and I'm changing some attributes influencing this label in another view controller on the navigation controller stack, this label doesn't get updated.
For example my label should show the amount of a budget position left saved in the entity SpendingCategory.
self.budgetLeftLabel.text = [NSString stringWithFormat:#"%# %#", [[self.spendingCategory getExpendituresAmount] getLocalizedCurrencyStringWithDigits:0], NSLocalizedString(#"left", nil)];
I derive this value from the category on SpendingCategory with this method:
- (NSNumber *)getExpendituresAmount
{
return [self.hasExpenditures valueForKeyPath:#"#sum.amount"];
}
However this label doesn't get any updates by the NSFetchedResultsController. And I have several locations in my app where this doesn't happen because a value is calculated. What do I need to change that these updates happen?
EDIT with datastructure:
Ok my Spending Category datastructure is roughly (for budget):
name (string)
cost (double)
position (integer 16)
Relationsships: hasExpenditures
My Expenditures structure (for tracking):
amount (double)
date (Date)
description (string)
Relationsships: forSpendingCategory
I hope it's clearer now. So why do these values not get updated?
The NSFetchedResultsController gets tickled when attributes in the relevant NSManagedObject instances are updated. If you are changing something that is purely calculated then the update never fires. Why is this relevant?
If you are changing something in the Expenditures entity (btw, entities should be singular in name) and you are watching the Spending Category entity then the NSFetchedResultsController won't fire because you didn't change anything that is relevant.
How to fix this?
Depends. I normally keep that derived value in the entity and persist it. Further, whenever a child changes a relevant value, I have the parent recalculate. This will cause the NSFetchedResultsController to fire.
How do you watch the values?
Either you have the child call a method on the parent (icky) or you have the parent watch the values on its children via KVO (better). Your personal preference decides here.
Update 1
To keep the derived value in the entity you add a new attributed to the entity and store it. Nothing is special about the attribute. It helps to keep in mind that Core Data is not a database. Core Data is your data model that happens to persist to a database if you so choose. Therefore you want to denormalize the database in cases like this.
while I was searching SO to find a good link for watching children, I stumbled across this example.
KVO object properties within to-many relationship
While the accepted answer is not very good, the second answer, using a NSFetchedResultsController is quite interesting and is worth exploring. The basic idea is that your parent objects instantiate a NSFetchedResultsController on -awakeFromFetch or -awakeFromInsert and when it fires, they recalculate the derived value. Thus the value is always up to date and your view controller based NSFetchedResultController instances will fire because the parent object has changed.
I did something similiar time ago, basically you need to store your calculated value in a transient attribute in your CoreData model, rather than implement your own setter and getter. Then in the related NSManagedObject you need to implement two methods:
// this will populate the values when
// the entity is retrieved from the store
-(void)awakeFromFetch {
[self refreshCellInfo];
}
// this will refresh the values when
// the object goes to fault
// (for example when it is off screen)
-(void)willTurnIntoFault {
[self refreshCellInfo];
}
-(void)refreshCellInfo {
// update all your derived values...
}
I am trying to do exactly same thing as post in NSFetchResultsController + sectionNameKeyPath + section order, i.e. basically use 2 tables, let's say Categories <-->> Events. Category table consists of category field only, while Event consists of name, dateTimestamp.
I defined relationship 'category' in Events table and try to use that relationship as sectionNameKeyPath when creating fetchedResultsController:
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"category.category" cacheName:#"Root"];
Finally, I pre-populated Category table with some categories upon loading of the app (and verified with .dump that table is populated correctly)
Yet, I simulator fails on:
return [[self.fetchedResultsController sections] count];
I did extensive search and most people either suggest using one of the fields in the table as sectionNameKeyPath (this works!) or transient property (works too!) However, I just want to use relationship as it seems very logical to me in this case where events belong to some categories and there could be categories without events. Am I wrong in my assumption that relationship can be used as sectionNameKeyPath? The original link at the top of the question suggests it works, but guy does not know why or how. Documentation is very weak on what can be used as sectionNameKeyPath, so any help will be highly appreciated.
A relationship gets you a pointer to a managed object. It seems logical, though, that the sectionNameKeyPath parameter should be a key path that leads to a string, since NSFetchedResultsSectionInfo's name property is a string. The fetched results controller will follow that key path for each fetched object and group the objects into sections based on what they return for that key path, and it'll also use those strings as the names of their respective sections. You can't use a managed object for the name -- you have to use some string property of the managed object.
So, your Category entity must have an attribute that distinguishes one category from another, right? Use that as the key path and (as you've seen) everything will work out.
BTW, I think it's useful to try to get out of the database (rows/fields) mindset and try to think in object-oriented terms like entity and attribute. A big selling point of Core Data is that it provides an abstraction layer that hides the storage mechanism. Thinking in terms of tables is like thinking about blocks and sectors when you're reading or writing a file.
Caleb, thank you for your answer. I do believe my understanding was wrong to some degree. What I had was an entity Category and entity Event. Category has a string field 'category', thus 'category.category' path (first 'category' is relationship in the Event entity)
What I did not take in account, though, is that if there are no events, fetchresultscontroller cannot fetch anything (similar to 'left join')
What I wanted is to show categories even if there are no events. Relationship 'category' will not return anything in this case as there is nothing to return/sort/categorize.
What I had to do (wrong or right - not sure yet) is to treat [managed] object created from Category entity as a separate object in case there are no events and place in the table. When there is one event per category, I can switch to the original method of [automatic] showing events sorted by categories.
This is interesting issue of starting point (empty entities with relationships) where I feel core data is more confusing than traditional relationship database. I also believe that's why all books/articles/reports carefully stay away from this topic. In other words, I could not find analog of "left join" in core data. May be I am wrong because I am relatively new to all this. Below is the description of the entities:
Category <-->> Event
Category - parent
Category.category - attribute of type String
Category.event - relationship to Event entity
Event - child
Event.name - attribute of type String
Event.category - relationship to Category entity
Each event belongs to one category. Category may have multiple events.
Categories should be shown even if there are no events for this category.
I was trying to put Events under fetchresultscontroller. May be I should switch to Category first and then calculate cell based on category.event relationship, not the other way around - did not try that yet.