In several data structures with my application I have an attribute named "IsPrimary". One issue I am having is that with for each User they can only have 1 primary address but have multiple address. What I am trying to figure out is with in cf9's implementation of hibernate how can I update any existing record that has the attribute IsPrimary as true to false if a new record or updated record is saved with IsPrimary being true.
This is my existing save method
public UserAddress function save(required UserAddress)
{
var userAcc = entityLoadByPK('UserAccount', arguments.UserAddress.getUserID());
arguments.UserAddress.setUserID(userAcc);
entitySave(arguments.UserAddress);
return arguments.UserAddress;
}
you may do it at preInsert() if you like.
Related
My DAC and tables are defined as follows
Data structure
ParentTableDAC (FormView)
ChildDac1 (TAb1/Grid)
ChildDac2 (Tab2/Grid)
in ChildDAc2, ChildDAc1ID need to be shown as Selector, How this can be done?
We are facing issue if Data is not saved for Parent/ChildDAc1 then it is not available to ChildDAc2 Lookup
Update -
Business Scenario -
A work item has Multiple Task and multiple Steps to perform the work item.
now WORKITEMDAC is a Parent
TASKDAC and STEPDAC are the Child of the Parent WORKITEMDAC.
OK till yet everything is ok and Usual..
Now each step is suppose to be linked with the Task of the Parent WORKITEM.
SO in STEPS Grid a Selector is required to select the TASK.
Here I have proper Parent child relationship there is no issue with that,I have only one issue that is, I can select only the TASK which was already saved with the Parent WORKITEM, unsaved Tasks are not displayed in the selector.
SO My Question was, do we have any way to get the Task in the PXSelector query which is not yet saved?
Following Selector is used on STEPDAC on TaskID Column -
[PXSelector(typeof(Search<TASKDAC.TaskID, Where<TASKDAC.taskID, Equal<Current>>>), typeof(TASKDAC.taskCD), SubstituteKey = typeof(TASKDAC.taskCD))]
Note - TaskID column in TASKDAC is identity column and this DAC has WorkItemID defined as PArent.
Update
Thanks for updating with more detail. Leaving the original response below for those that may need it regarding parent/child.
I've never seen the selector defined with reference to Current<> and no Current WHAT. To be honest, I'm surprised it compiled. "Where Field = Current View Field" directs the selector to limit results to only values associated to the Current value of whatever field you specified, and it is retrieved from the "ViewName.Current" record in the graph. Your where clause does not tell it what "current" to match. Since you direct the selector to Search, I suspect that this is causing the selector to be unable to find a matching record and short-circuiting the save of the result for that field.
It sounds like TASKDAC is a "master data" type record, defining all possible tasks to be performed. Therefore, TaskID is not a key field in STEPDAC but rather a key field in TASKDAC. If you want to select "any" task, you don't need the where clause at all.
[PXSelector(
typeof(TASKDAC.TaskID),
typeof(TASKDAC.taskCD),
SubstituteKey = typeof(TASKDAC.taskCD)
)]
Let's assume for a moment that you designate a WORKITEMDAC.WorkOrderType that is used to filter acceptable tasks by the same field. (Only repair tasks can be assigned to a repair WorkOrderType, assembly to an assembly WorkOrderType, etc.) In this case, you would use the syntax you stated, but you would designate Where<TASKDAC.WorkOrderType, Equal<Current<WORKITEMDAC.WorkOrderType>>. If the task is limited to something in the STEPDAC instead, just swap out WORKITEMDAC with the STEPDAC field in the example below.
[PXSelector(
typeof(Search<TASKDAC.TaskID,
Where<TASKDAC.WorkOrderType, Equal<Current<WORKITEMDAC.WorkOrderType>>>>),
typeof(TASKDAC.taskCD),
SubstituteKey = typeof(TASKDAC.taskCD)
)]
Please update your PXSelector and advise if that fixes the issue or what error you get next.
Original Response
With so little to your question, the nature of your question is very unclear. It sounds like you are attempting to do something that should perhaps take a different approach. The definition of a selector should not impact the saving of a field, but the structure of a parent child relationship is critical to define properly. As such, it seems very odd that you would have a Child ID that differs from the parent and that it would be used in a selector.
Typically, the child DAC shares an ID with the parent DAC. We often define a LineNbr field in the child which is given a value by PXLineNbrAttribute, and the Key fields would be ID + LineNbr to identify a specific child record. We also set SyncPosition = true in the ASPX document on the grid so that the user interface stays completely sync'd with whichever record the user has selected in the grid. Parent Child relationships can be daisy chained, such as with SOOrder -> SOLine -> SOLineSplit, but ultimately, the ID field of a child is not a selector... the ID of the parent is.
Think of a selector as a means of looking up master data. Master data may be related to other data, but it would not have a parent in most cases. There are exceptions, but let's keep it simple. A purchase order, for instance, would not have parent, and the key field would be a common selector field. However, since a child must always relate to its parent and cannot change its parent, the ID of the child field would not be a selector. Other tables in Acumatica would refer back to some field or combination of fields to relate to the data of a child DAC.
From the T210 training guides, I stripped away a lot to show just this point.
RSSVRepairPrice - Parent
using System;
using PX.Data;
namespace PhoneRepairShop
{
[PXCacheName("Repair Price")]
public class RSSVRepairPrice : IBqlTable
{
#region PriceID
[PXDBIdentity]
public virtual int? PriceID { get; set; }
public abstract class priceID : PX.Data.BQL.BqlInt.Field<priceID> { }
#endregion
#region ServiceID
[PXDBInt(IsKey = true)]
[PXDefault]
[PXUIField(DisplayName = "Service", Required = true)]
[PXSelector(
typeof(Search<RSSVRepairService.serviceID>),
typeof(RSSVRepairService.serviceCD),
typeof(RSSVRepairService.description),
DescriptionField = typeof(RSSVRepairService.description),
SelectorMode = PXSelectorMode.DisplayModeText)]
public virtual int? ServiceID { get; set; }
public abstract class serviceID : PX.Data.BQL.BqlInt.Field<serviceID> { }
#endregion
#region RepairItemLineCntr
[PXDBInt()]
[PXDefault(0)]
public virtual Int32? RepairItemLineCntr { get; set; }
public abstract class repairItemLineCntr : PX.Data.BQL.BqlInt.Field<repairItemLineCntr> { }
#endregion
}
}
RSSVRepairItem - Child
using System;
using PX.Data;
using PX.Objects.IN;
using PX.Data.BQL.Fluent;
namespace PhoneRepairShop
{
[PXCacheName("Repair Item")]
public class RSSVRepairItem : IBqlTable
{
#region ServiceID
[PXDBInt(IsKey = true)]
[PXDBDefault(typeof(RSSVRepairPrice.serviceID))]
[PXParent(
typeof(SelectFrom<RSSVRepairPrice>.
Where<RSSVRepairPrice.serviceID.IsEqual<
RSSVRepairItem.serviceID.FromCurrent>>
))]
public virtual int? ServiceID { get; set; }
public abstract class serviceID : PX.Data.BQL.BqlInt.Field<serviceID> { }
#endregion
#region LineNbr
[PXDBInt(IsKey = true)]
[PXLineNbr(typeof(RSSVRepairPrice.repairItemLineCntr))]
[PXUIField(DisplayName = "Line Nbr.", Visible = false)]
public virtual int? LineNbr { get; set; }
public abstract class lineNbr : PX.Data.BQL.BqlInt.Field<lineNbr> { }
#endregion
}
}
In the parent, notice the use of PXDBIdentityAttribute which specifies that the record identity is set by the database in PriceID. The "ID" field used by the child is taken from the ServiceID field which is defined as a selector for another master table. The use of IsKey = true on PXDBIntAttribute tells Acumatica that it can find the unique record by the combination of all the fields marked IsKey = true. (Note that the actual training guide includes another field as part of the key, but I simplified the example here.) Also notice the use of a PDXBInt field that has a default of 0 to be used as a Line Counter. (RepairItemLineCtr)
Now, in the child, notice that the key field of the parent is defined again in the DAC, but without the selector. The PXParentAttribute defines the relationship back to the parent DAC, and the field value is defaulted in from that parent. This is what causes the value to be saved in the record automatically. It also associates the relationship so that deleting the parent will delete the child as well. Notice the use of PXLineNbrAttribute to autoincrement the field of the parent DAC and set the value in the LineNbr field. It is the combination of these 2 fields that points you to the exact record.
There are cases where Acumatica uses the NoteID field (a unique GUID value) to identify a record of a child DAC, but this is a bit more complex. You can see it in the relationship of INItemPlan (Demand) to Purchase Orders.
If you are having trouble with Parent-Child (Master-Detail) relationships, I'd highly recommend reviewing the T210 training course.
To refer the unsaved data use the ISDirty =true on PXSelector.
also to save the Identity key valueof the unsaved data at referenced place, use [PXDBDefault()]
I have a view for a location. This location, that has different services. They are both linked as has many in the model (n-m relation) in the model LocationService.
In this view, it is possible to select different tags for each service of the location. I have a model called location_service_tag, that has the id of the location_service and the id of the tags.
Now I want to store the tags of the location_service combination using the sync method. How is it possible to store this information?
Here is an example (The IDs are free chosen):
I have the view with the location 1.
This location has 2 services with the id 11, 22.
They are stored in location_service with the ids 111,122.
The location_service 111 has the tag id 1111, 2222, the location_service 122 has the tags 2222,4444.
Now I want to store these tags into location_service_tag using sync. How is this possible?
I thought it might be possible somehow like this, but it is not.
foreach($request->servicetags as $servicetag){
if(count($servicetag)>0){
//Final statement must be true, because we want to override e.g. if one is deleted or inserted
$location->locationservice()->tags()->sync($servicetag, true);
}else{
//There are no services, submit an empty array
$location->locationservice()->tags()->sync([], true);
}
}
Finally I did it with:
//Save relational Data of services
//If there are services, add the, else add empty array
if(isset($request->locationservices)){
foreach($request->locationservices as $key => $locationservice){
if(count($locationservice)>0){
LocationService::find($key)->tags()->sync($locationservice, true);
}else{
LocationService::find($key)->tags()->sync([], true);
}
}
}
The key has the locationservice_id as key and the value of the loop has the selected tag_ids as an array for each locationservice.
I am trying to fetch items from the dynamoDb. However when I fetch single item using partiton key everything works fine but when I try to fetch all the items in dynamo db using scan I encouter an error. Following is the code what I am trying to do:
public List<PageCmsDomain> getAllPages() {
DynamoDBUtil dynamoDBUtil = new DynamoDBUtil();
AmazonDynamoDB dynamoDBClient = dynamoDBUtil.getDynamoDBClient();
DynamoDBMapper mapper = new DynamoDBMapper(dynamoDBClient);
List<PageCmsDomain> scanResult = mapper.scan(PageCmsDomain.class, new
DynamoDBScanExpression());
return scanResult;
Following error comes when I execute this code:
com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMappingException: PageCmsDomain[restrictions]; could not unconvert attribute
at com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperTableModel.unconvert(DynamoDBMapperTableModel.java:271)
at com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper.privateMarshallIntoObject(DynamoDBMapper.java:456)
at com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper.marshallIntoObjects(DynamoDBMapper.java:484)
at com.amazonaws.services.dynamodbv2.datamodeling.PaginatedScanList.<init>(PaginatedScanList.java:64)
at com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper.scan(DynamoDBMapper.java:1458)
at com.amazonaws.services.dynamodbv2.datamodeling.AbstractDynamoDBMapper.scan(AbstractDynamoDBMapper.java:216)
at com.astro.ott.dynamodb.cmsapi.impl.PageCmsDaoImpl.getAllPages(PageCmsDaoImpl.java:74)
at com.astro.ott.cmsapi.impl.PageCmsGetServiceImpl.getPages(PageCmsGetServiceImpl.java:41)
at com.astro.ott.cms_api.cms_api.PageCmsTestCase.get(PageCmsTestCase.java:81)
Caused by: com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMappingException: not supported; requires #DynamoDBTyped or #DynamoDBTypeConverted
at com.amazonaws.services.dynamodbv2.datamodeling.StandardModelFactories$Rules$NotSupported.get(StandardModelFactories.java:660)
at com.amazonaws.services.dynamodbv2.datamodeling.StandardModelFactories$Rules$NotSupported.get(StandardModelFactories.java:650)
at com.amazonaws.services.dynamodbv2.datamodeling.StandardModelFactories$AbstractRule.unconvert(StandardModelFactories.java:714)
at com.amazonaws.services.dynamodbv2.datamodeling.StandardModelFactories$AbstractRule.unconvert(StandardModelFactories.java:691)
at com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTypeConverter$DelegateConverter.unconvert(DynamoDBTypeConverter.java:109)
at com.amazonaws.services.dynamodbv2.datamodeling.StandardTypeConverters$Vector$ToMap.unconvert(StandardTypeConverters.java:433)
at com.amazonaws.services.dynamodbv2.datamodeling.StandardTypeConverters$Vector$ToMap$1.unconvert(StandardTypeConverters.java:417)
at com.amazonaws.services.dynamodbv2.datamodeling.StandardTypeConverters$Vector$ToMap$1.unconvert(StandardTypeConverters.java:410)
at com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTypeConverter$DelegateConverter.unconvert(DynamoDBTypeConverter.java:109)
at com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTypeConverter$NullSafeConverter.unconvert(DynamoDBTypeConverter.java:128)
at com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTypeConverter$ExtendedConverter.unconvert(DynamoDBTypeConverter.java:88)
at com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperFieldModel.unconvert(DynamoDBMapperFieldModel.java:146)
at com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperFieldModel.unconvertAndSet(DynamoDBMapperFieldModel.java:164)
at com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperTableModel.unconvert(DynamoDBMapperTableModel.java:267)
... 32
And I am using same PageCmsDomain while fetching single item also.
Specified code will work only if restrictions field is null or empty map. Otherwise it will throw exception as DynamoDB does not allow to save or fetch objects that have field with Object type (even as a generic type, in this case - map's value type Object), instead it should have specific type (e.g. String, Integer, Map<String, Integer>).
If restrictions map's value type can't be changed to specific one,
you can fix that by adding DynamoDBTypeConvertedJson annotation to store serialized value of restrictions field:
#DynamoDBAttribute(attributeName = "restrictions")
#DynamoDBTypeConvertedJson
private Map<String, Object> restrictions;
Another option is to specify your own custom converter class and annotate restrictions field with
#DynamoDBTypeConverted(converter = CustomConverter.class)
I have been wondering why my mappings and the controller actions are not working. For this I need to refer to my previous post, where I have described my entities and database schema, which can be found here. I need to start a new post since there were no further updates and I thought this the only way to get attention of the Doctrine + Zend Pros.
As described in my previous post, I have a Zend Form, the user can enter teamId and teamName, further he has the choice to select multiple players from the drop down list on the form and can allocate players to the team. So basically that is my goal to achieve with Doctrine and Zend. For that I wrote my entities described in the previous post and right now I want to add the code from my controller to persist the entities.
Controller:
public function addAction()
{
$form = new TeamForm($this->getEntityManager());
$form->get('submit')->setAttribute('value', 'Add');
$request = $this->getRequest();
if ($request->isPost())
{
$team = new Team();
$player = new Player();
$teamPlayers = new TeamPlayer();
$form->setInputFilter($typeset->getInputFilter());
$form->setData($request->getPost());
if ($form->isValid())
{
$team->populate($form->getData());
$teamPlayers->setPlayer($player);
$teamPlayers->setTeam($team);
$this->getEntityManager()->persist($teamPlayers);
$this->getEntityManager()->flush();
//Reroute to the index page once the data is successfully added
}
}
//return form array
return array(
'form' => $form
);
}
So that is basically what Im doing in my controller to save the entities into two tables (team Table and teamPlayer Table), as already the player Table is populated with data. So I want to add players to the team and assign that values in these two tables.
Right now I can see my form and when I enter the data and press submit nothing is happening, i can see the form with no action. When the data is successfully saved into the database then I reroute it to the index page which is not happening.
Any help would be appreciated, to point out the error Im making either in mapping section or in the controller side.
The official documentation especially from Doctrine 2 is too global and is particulary not that clear for my requirement.
let's try to solve it by updating this answer by steps :
step 1 :
your words implied to me that , you might have some issues in from validation , so lets check if you are passing this $form->isValid()
if ($form->isValid())
{
$team->populate($form->getData());
$teamPlayers->setPlayer($player);
$teamPlayers->setTeam($team);
$this->getEntityManager()->persist($teamPlayers);
$this->getEntityManager()->flush();
//Reroute to the index page once the data is successfully added
}else{
var_dump($form->getMessages());
}
I can also suggest you to use doctrine commandline : doctrine orm:validate-schema , this command will help you to check if your entity mapping is ok plus your database mapping is also okay , i see it as handy to to debug your doctrine2 entity
ps : I havn't read your entities in depth yet
i tried to implement the file upload via doctrine/lifecycle callbacks as described here:
http://symfony.com/doc/current/cookbook/doctrine/file_uploads.html#using-lifecycle-callbacks
So far it works, but the PrePersist/PreUpdate Event is not fired, the function "preUpload" is not called.
Functions like "upload" and "removeUpload" triggered by other lifecycle events are called correctly.
Does anyone have an idea why the event is not fired or a solution for this problem?
Thanks
I have another solution to this problem:
My entity has a field "updatedAt" which is a timestamp of the last update. Since this field gets set anyway (by the timestampable extension of Gedmo) I just use this field to trick doctrine into believing that the entitiy was updated.
Before I persist the entity I set this field manually doing
if( $editForm['file']->getData() )
$entity->setUpdateAt(new \DateTime());
This way the entity gets persisted (because it has changed) and the preUpdate and postUpdate functions are called properly.
Of course this only works if your entity has a field that you can exploit like that.
You need to change tracking policies.
Full explanation.
there's a much simpler solution compared with changing tracking policies and other solutions:
in controller:
if ($form->isValid()) {
...
if ($form->get('file')->getData() != NULL) {//user have uploaded a new file
$file = $form->get('file')->getData();//get 'UploadedFile' object
$news->setPath($file->getClientOriginalName());//change field that holds file's path in db to a temporary value,i.e original file name uploaded by user
}
...
}
this way you have changed a persisted field (here it is path field), so PreUpdate() & PostUpdate() are triggered then you should change path field value to any thing you like (i.e timestamp) in PreUpdate() function so in the end correct value is persisted to DB.
A trick could be to modify the entity no matter what..on postLoad.
1 Create an updatedAt field.
/**
* Date/Time of the update
*
* #var \Datetime
* #ORM\Column(name="updated_at", type="datetime")
*/
private $updatedAt;
2 Create a postLoad() function that will modify your entity anyway:
/**
* #ORM\PostLoad()
*/
public function postLoad()
{
$this->updatedAt = new \DateTime();
}
3 Just update that field correctly on prePersist:
/**
* #ORM\PrePersist()
* #ORM\PreUpdate()
*/
public function preUpload()
{
$this->updatedAt = new \DateTime();
//...update your picture
}
This is basically a slight variation of #philipphoffmann's answer:
What i do is that i modify an attribute before persisting to trigger the preUpdate event, then i undo this modification in the listener:
$entity->setToken($entity->getToken()."_tmp");
$em->flush();
In my listener:
public function preUpdate(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
if ($entity instanceof MyEntity) {
$entity->setToken(str_replace('_tmp', '', $entity->getToken()));
//...
}
}
Another option is to display the database field where the filename is stored as a hidden input field and when the file upload input changes set that to empty so it ends up triggering doctrine's update events. So in the form builder you could have something like this:
->add('path', 'text', array('required' => false,'label' => 'Photo file name', 'attr' => array('class' => 'invisible')))
->add('file', 'file', array('label' => 'Photo', 'attr' => array('class' => 'uploader','data-target' => 'iddp_rorschachbundle_institutiontype_path')))
Path is a property managed by doctrine (equal to the field name in the db table) and file is the virtual property to handle uploads (not managed by doctrine). The css class simply sets the display to none. And then a simple js to change the value of the hidden input field
$('.uploader').change(function(){
var t = $(this).attr('data-target');
//clear input value
$("#"+t).val('');
});
For me, it worked good when I just manually called these methods in the controller.
Do you have checked your metadata cache driver option in your config.yml file?If it exists, just try to comment this line:
metadata_cache_driver: whateverTheStorage
Like this:
#metadata_cache_driver: whateverTheStorage