Defaulting user-defined fields for records created before the customization - acumatica

I have a DAC extension that has a default attribute set for it(see definition below). I have noticed that for any records that existed prior to the DAC extensions existence will still hold a null value. Is there a conventional way that we can get all the records initialized?
If not I will likely perform a check within an event handler before its used.
Thanks in advance!
public abstract class usrCustOptInOut : IBqlField
{
}
protected string _UsrCustOptInOut;
[PXDBString(1, IsUnicode = true)]
[PXDefault(OptInOut.Default, PersistingCheck = PXPersistingCheck.Nothing)]
[PXUIField(DisplayName = "Click To Pay Opt In/Out")]
[PXStringList(
new[]
{
OptInOut.OptOut,
OptInOut.OptIn,
OptInOut.Default
},
new[]
{
"Customer Opt Out",
"Customer Opt In",
"Default"
})]
public virtual string UsrCustOptInOut
{
get
{
return _UsrCustOptInOut;
}
set
{
_UsrCustOptInOut = value;
}
}

You will need to run an update manually. Existing records will not be updated automatically when adding new fields and records exist.
I see two options:
Add a a customization plugin to your project (preferred)
Add a custom SQL script to your project
Option 1
You can use a customization plugin to make sure all null values have a default. The customization plugin will run at the end of each publish within the site.
You can create a code file in your customization project to this:
The UpdateDatabase() method in a CustomizationPlugin runs after the customization was published and the website was restarted (the Usr field will be applied).
Then you can use PXDatabase.Update to run the update without needing a graph or any validation/events to run. You just need a bulk SQL update and PXDatabase.Update will do the job. The example below will only set a default if there is a null value. The same concept can be applied to any table/field added not just user fields. For example if you have a custom table and you need to add a new field and have existing records contain a default.
A down side (when compared to Option 2) is PXDatabase.Update will run for the current tenant (company) as it appends the current CompanyID to the SQL statement (which is a good thing). If you have multiple tenants (companies) you will need to run the publish for multiple tenants. Alternativly you can write your code so that it will loop each company and run your statement using PXLoginScope (see references).
The end result using a Customization Plugin would look something like this:
public class UsrFieldDefaults : CustomizationPlugin
{
//This method executed after customization was published and website was restarted.
public override void UpdateDatabase()
{
PXDatabase.Update<MyDac>(
new PXDataFieldAssign<MyDacExt.usrCustOptInOut>(PXDbType.NChar, 1, OptInOut.Default),
new PXDataFieldRestrict<MyDacExt.usrCustOptInOut>(PXDbType.NChar, 1, null, PXComp.ISNULL)
);
}
}
Option 2
You can write a custom SQL script to do the same and put it in your customization project. Keep in mind which DBMS you are publishing to (MSSQL/MYSQL). There is also a way to use a shared SQL syntax so it should work for any DBMS. Also be aware that you would most likely run the script for all tenants (companies) which would include snapshots.
Additional References:
Custom Processes During Publication of a Customization
To Add a Customization Plug-In to a Project
Save data to different company
To Publish a Customization for a Multitenant Site
Creating a Custom SQL Script
Writing Custom SQL Scripts for Interpretation

Related

Acumatica CREATE VIEW to create SQL view and load it as a DAC for use in GI & Reports

Similar post to this:
Curious about properly defining the DAC.
I have a T-SQL CREATE VIEW script that I've added to the 'Database Scripts' are of the Customization project as shown. But I suspect that I haven't added the DAC properly. When I attempt to add this DAC within the context of a Generic Inquiry 'Add Related table' screen I do not get any results when searching for the name of the DAC I tried to create.
When I initially attempted to define the DAC in the 'Code' area, the system complained that the object didn't exist in the database. This makes sense as it's a SaaS instance and I've just created the T-SQL view from a local copy of the database.
So I just added the CREATE VIEW statement within the 'Database Scripts' area and published the customization successfully.(This implies to me that the object exists in the database now).
But I now believe I need to retroactively 'Generate Members from Database' in order to identify the key fields so Acumatica can see how it aligns with other DACs. Is this a correct assumption?
In the CODE area I see this (where vGFCINItemClassSeg is the name of the SQL view )
using System;
using PX.Data;
namespace vGFCINItemClassSeg
{
[Serializable]
[PXCacheName("vGFCINItemClassSeg")]
public class vGFCINItemClassSeg : IBqlTable
{
}
}
Any suggestions on how to properly provide Acumatica with what it needs to associate this to the INItemClass table in the database and make it available as a DAC?
Got this resolved.
Problem was that I had attempted to add the DAC prior to adding the Database script.
Solution: unpublish the customization package. drop the custom SQL view from the database. Start a new customization package. Add the Database Script first. THEN publish. Then after that is successful, re-open the customization project and add the DAC as a second step.

Make a Field Mandatory on the Graph Level

Newbie to Acumatica here. I've performed a small amount of customization to our system, and am now diving into adding custom data fields.
My goal is to synchronize hardware shipment information from Acumatica into our legacy (outdated and proprietary) hardware management system, as we will need to continue using this system for the time being for warranty calculations. I plan to eventually build this into Acumatica.
My current issue is that I need a method of associating Customer Locations to the customer locations in our legacy system. Adding the field DCL_ID was easy enough to accomplish following the To Add a Custom Data Field documentation. I made the column be required by setting
[PXDefault]
[PXUIField(DisplayName="DCL Account ID", Required = true)]
to the attributes section of the Data Access class as outlined here. I then added the field to my form using the Layout Editor.
At this point all seemed well. The field shows an asterisk in the UI and also validates that a value is provided. Then I realized that Customer Locations is not the only place that uses CR.Location -- it is also used by Account Locations. Doing some digging I've found that Account Locations can include many more account types than Customer Locations. I only need this attribute to be required for Customer Locations. Thus, I have opted to use the To Make a Field Mandatory on the Graph Level.
Here is my CustomerLocationMaint code:
using System;
using PX.Data;
using PX.Objects.CR;
using System.Collections.Generic;
using PX.Objects;
using PX.Objects.AR;
namespace PX.Objects.AR
{
public class CustomerLocationMaint_Extension : PXGraphExtension<CustomerLocationMaint>
{
#region Event Handlers
[PXDefault]
[PXCustomizeBaseAttribute(typeof(PXUIFieldAttribute), "Required", true)]
protected virtual void SelectedCustomerLocation_UsrDCL_ID_CacheAttached(PXCache cache)
{
}
#endregion
}
}
After I save and publish the customization, the field does not function as a required field, as it did when I defined the requirements at the DAC level.
So, what have I done wrong? I've read and re-read the documentation multiple times, but cannot find my mistake.
Setup:
My thought is the underscore in the field name causing the cache attached to not properly register the graph level attribute change. Using a field name without the underscore is the preferred naming convention for tables and columns.
The Acumatica documentation mentions this should be avoided as listed here:
Database Design Guidelines
Found under Table and Column Naming Conventions:
Do not use the underscore symbol (_) in table or column names, because
it is a reserved symbol in Acumatica Framework. For example,
CompanyType is a valid column name, while Company_Type is invalid.

OrchardCMS Infoset and partrecord getting out of sync

I'm finding that my partrecord (backing record) and infoset record are getting out sync, when using ContentItemVersionRecord.
The site has been developed using dynamic forms, and custom part records, such as...
public class ParticipantPartRecord : ContentPartVersionRecord
{
public virtual string ParticipantId { get; set; }
}
public class ParticipantPart : ContentPart<ParticipantPartRecord>, ITitleAspect
{
public string ParticipantId {
get { return Retrieve(r => r.ParticipantId); }
set { Store(s => s.ParticipantId, value); } }
}
When updating (I'm using a fork of the dynamic forms module which allows edits - see issue 6163) a record through a DynamicForm the FormController creates a draft of the ContentItem, updates the bindings, and publishes the ContentItem, which updates the infoset record in ContentItemVersionRecord and (I assume) triggers an update of the backing record.
There are some fields which I update via workflows (as they are generated programatically, rather than user set on the form). When I update these records (on the previously published draft, so latest version), the infoset updates, however the backing record does not seem to.
As most of the data retrieval is using _contentManager.Get(id) I hadn't noticed the issue, however when using _contentManager.Query.Where() on the programatically updated records, I discovered that the backing record no longer matched the InfoSet record in Orchard_Framework_ContentItemVersionRecord
When updating the records programatically in the workflows I'm using code like
var participant = workflowContext.Content.ContentItem.As<ParticipantPart>();
participant.ParticipantId = GenerateParticipantId();
This updates the InfoSet record in ContentItemVersionRecord, but the backing record does not appear to be updating.
I haven't used VersionedRecords before, and I wonder if I need to be creating a draft, and publishing in order to force the backing record to update, or if there is something in the Handler that needs to be set to trigger the update to the backing record.

Versioning for ContentPart's without a record

Is it possible to create draftable versions of content parts that have no record.
For example
public MyContentPart : ContentPart
{
...
}
When I create a draftable version of MyContentPart it doesn't behave the same as a part that inherits from ContentPart<MyContentPartVersionRecord> would. E.g. change some values of it's properties and still see the original values in the last published version.

adding config items in Orchard Module

I was wondering on how to add configurable items in a Orchard module (analogous to appsettings key-value in a webapp). Could anyone me point to the right source/url in the web ?
Orchard has a rich settings infrastructure. It is not as simple to implement as the appsettings.config, but is a lot more powerful. You can add settings to the site (shown in the main 'settings' section of the dashboard) or for a specific content type, content part, field or even a part when it is attached to a specific content type. You can define custom settings parts so that your settings can be managed by end users through the dashboard.
For an example of both site settings and content type settings you can look at the Orchard.Comments module. There are also some good blog posts on creating your own custom settings in your module:
http://www.szmyd.com.pl/blog/how-to-add-settings-to-your-content-parts-and-items
http://www.szmyd.com.pl/blog/using-custom-settings-in-orchard-part-2-content-type-settings
I think the proper way to achieve this is to create a Record and leverage orchards database migration functionality. Orchard does the heavy lifting and you end up with a strongly typed object and repository! I believe the examples on the Orchard site use this technique for the custom Content Types but you can create them without just create your model
public class Item {
public string Key {get;set;}
public string Value {get;set;}
}
Then in the migration define your table with a table name equal to your model name
SchemaBuilder.CreateTable("Item",
table => table
.Column<string>("Key",column => column.PrimaryKey().Identity())
.Column<string>("Value", column => column.NotNull()
.WithLength(30)
Then you can inject a repository where yu need it
public class MyDriver : ContentFieldDriver<MyPart>
{
public MyDriver(IRepository<Item> itemRepository) {... }
Thats it!

Resources