Skip to main content
Question

Duplicate Record Error When Loading and Saving Organization-Specific Setup Records

  • October 22, 2026
  • 1 reply
  • 22 views
  • Translate All

Hi,

I'm developing a custom setup screen in Acumatica 2025 R2 (Modern UI) that maintains configuration settings at the organization level.

The requirement is to maintain one setup record per OrganizationID. Multiple branches belonging to the same organization should access and update the same configuration record.

I'm experiencing an issue where the existing record is successfully retrieved from the database and displayed on the screen. However, when I modify a field and attempt to save it for the first time, Acumatica throws:

Error: Another process has added the 'CustomOrgSetup' record. Your changes will be lost.

Interestingly, when I retry the operation, the record saves successfully.

I'm trying to understand whether my custom view delegate is interfering with Acumatica's normal cache and persistence handling.
 

1. DAC Definition

OrganizationID is the primary key.

[Serializable]
[PXCacheName("Custom Organization Setup")]
public class CustomOrgSetup : PXBqlTable, IBqlTable
{
#region OrganizationID
[PXDBInt(IsKey = true)]
[PXDefault]
[PXUIField(DisplayName = "Organization", Visible = false)]
public virtual int? OrganizationID { get; set; }

public abstract class organizationID
: PX.Data.BQL.BqlInt.Field<organizationID>
{
}
#endregion
}

2. Graph – Primary View

The setup record is filtered based on the organization associated with the currently selected branch.

public PXSave<CustomOrgSetup> Save;
public PXCancel<CustomOrgSetup> Cancel;

public SelectFrom<CustomOrgSetup>
.InnerJoin<PX.Objects.GL.Branch>
.On<PX.Objects.GL.Branch.organizationID
.IsEqual<CustomOrgSetup.organizationID>>
.Where<PX.Objects.GL.Branch.branchID
.IsEqual<AccessInfo.branchID.FromCurrent>>
.View Setup;

public PXFilter<CustomOrgSetupDialog> SetupDialog;

public PXFilter<CustomOrgSetupDialog> UserSyncDialog;

3. Custom View Delegate

I'm using the following delegate to load the current organization's setup record.

protected virtual IEnumerable setup()
{
int? organizationID =
PXAccess.GetParentOrganizationID(
Accessinfo.BranchID);

if (organizationID == null)
{
throw new PXException(
"Organization not found.");
}

// First check the graph cache.
// Preserve unsaved changes.
CustomOrgSetup cachedRow =
Setup.Cache.Cached
.Cast<CustomOrgSetup>()
.FirstOrDefault(
x => x.OrganizationID == organizationID);

if (cachedRow != null)
{
PXEntryStatus status =
Setup.Cache.GetStatus(cachedRow);

if (status == PXEntryStatus.Updated)
{
yield return cachedRow;
yield break;
}
}

CustomOrgSetup row = GetSetup(this);

if (row != null)
yield return row;
}

The helper method retrieves the existing database record:

private static CustomOrgSetup GetSetup(PXGraph graph)
{
int? organizationID =
PXAccess.GetParentOrganizationID(
graph.Accessinfo.BranchID);

if (organizationID == null)
{
throw new PXException(
"Organization not found.");
}

return SelectFrom<CustomOrgSetup>
.Where<
CustomOrgSetup.organizationID
.IsEqual<@P.AsInt>>
.View.ReadOnly
.Select(graph, organizationID);
}

4. Observed Behaviour

  1. On initial screen load, the GetSetup() method successfully retrieves the existing database record, including previously saved field values.

  2. When I modify a field and attempt to save, Acumatica throws the duplicate-record error.

  3. Retrying the operation sometimes allows the record to be saved successfully.

  4. If I remove the custom setup() delegate, the existing record does not load as expected.

  5. If I use .View.ReadOnly on the primary view, the record is displayed, but the fields are not editable.

  6. Using a normal .View.Select() instead of .View.ReadOnly.Select() in the delegate did not consistently retrieve the existing database values.

I also inspected the record during RowPersisting and received:

OrganizationID: 5
Operation: Normal, Update
Status: Updated

This suggests the record is marked as Updated during persistence, despite the duplicate-record error.

5. Questions

  1. Is using .View.ReadOnly.Select() inside a custom delegate for an editable primary view causing the first-save issue?

  2. What is the recommended approach for loading an existing organization-specific setup record while preserving unsaved changes?

  3. Is there a way to define an editable primary view filtered by the current branch's OrganizationID without using a custom delegate?

  4. Why might Acumatica throw the duplicate-insert error on the first attempt but successfully save on the second attempt, even though RowPersisting reports an Update operation?

  5. Is there a recommended native Acumatica setup-screen pattern for handling both new and existing organization-level records through standard PXSave functionality?

I've also reviewed native setup screens such as GLSetupMaint (GL102000), but those appear to follow a simpler setup structure without organization-based filtering.

Ideally, I would like to use Acumatica's standard record loading, caching, and persistence behavior without manually managing cache statuses or overriding the Save process.

Any suggestions or examples would be greatly appreciated.

Thank you!

 

1 reply

Steve Milner
Varsity III
Forum|alt.badge.img+6

@vishalr53 It isn't the ReadOnly select. Your screen opens in insert mode.

I built your DAC and graph on 2025 R2 Modern UI with logging on ExecuteInsert, ExecuteUpdate and Persist. Because CustomOrgSetup is the primary view and has a key, the form calls Insert on Setup as soon as it opens, before any select. That blank row sits in the cache as Inserted. Your delegate only returns Updated rows, so you see the ReadOnly database row instead. Your first edit then loads org 5 from the database and marks it Updated, and Save tries the blank insert first.

With your DAC exactly as posted, that insert fails with "'OrganizationID' cannot be empty". Something in your full code must fill OrganizationID on insert, because once it defaults from the branch I get your exact message. That error also clears the inserted row from the cache, so the retry is a plain update. That's the Normal, Update / Updated you logged.

The same row explains your other two results. With no delegate, or with a normal Select, the cache hands back the blank inserted row instead of the database record.

What worked is the shape Inventory Preferences (IN101000) uses for its per-branch INScanSetup: a keyless primary view, and the keyed record in a second view whose delegate returns the row or inserts one.

// DAC: default the key from the current branch's company
[PXDBInt(IsKey = true)]
[PXDefault(typeof(Search<Branch.organizationID,
    Where<Branch.branchID, Equal<Current<AccessInfo.branchID>>>>))]
[PXUIField(DisplayName = "Organization", Visible = false)]
public virtual int? OrganizationID { get; set; }

// Keyless header for the primary view (Organization is PX.Objects.GL.DAC.Organization)
[PXHidden]
public class CustomOrgSetupFilter : PXBqlTable, IBqlTable
{
    [PXInt]
    [PXUnboundDefault(typeof(Search<Branch.organizationID,
        Where<Branch.branchID, Equal<Current<AccessInfo.branchID>>>>))]
    [PXSelector(typeof(Search<Organization.organizationID>),
        SubstituteKey = typeof(Organization.organizationCD),
        DescriptionField = typeof(Organization.organizationName))]
    [PXUIField(DisplayName = "Company", Enabled = false)]
    public virtual int? OrganizationID { get; set; }
    public abstract class organizationID : PX.Data.BQL.BqlInt.Field<organizationID> { }
}

// Graph: the filter is the primary view, the record is a second view
public PXSave<CustomOrgSetupFilter> Save;
public PXCancel<CustomOrgSetupFilter> Cancel;
public PXFilter<CustomOrgSetupFilter> Filter;

public SelectFrom<CustomOrgSetup>
    .Where<CustomOrgSetup.organizationID
        .IsEqual<CustomOrgSetupFilter.organizationID.FromCurrent>>
    .View Setup;

protected virtual IEnumerable setup()
{
    CustomOrgSetup row = SelectFrom<CustomOrgSetup>
        .Where<CustomOrgSetup.organizationID
            .IsEqual<CustomOrgSetupFilter.organizationID.FromCurrent>>
        .View.Select(this);
    return new CustomOrgSetup[] { row ?? Setup.Insert() };
}

In TS, set primaryView: "Filter", add Setup = createSingle(CustomOrgSetup), and bind your settings fieldset with view.bind="Setup".

I tested it on one company from both of its branches, where the first save updated the shared record, and on a company with no record yet, where the first save inserted it.

Translate