Skip to main content
Answer

Migration Customization - Customer Objects

  • June 11, 2025
  • 1 reply
  • 63 views

Has anyone successfully created new customers programmatically in Acumatica?
I'm trying to understand the minimum required fields, whether I can default values from a Customer Class, and what the best practices are.

Yes, I’m aware that import scenarios are the standard approach—but in this case, they’re not sufficient. I’m importing from another accounting system and have custom logic in place for remapping, consolidating, and creating new customers, which import scenarios can’t handle. This process also needs to run automatically every few hours, so I’m looking for a robust, code-based solution.

Any examples or guidance would be appreciated.

Here is my starting point….

 

// ——————— Create Acumatica Customers (Comprehensive Explicit Creation) ———————

[PXButton(CommitChanges = true)]

[PXUIField(DisplayName = "Create Customers")]

public IEnumerable createCustomers(PXAdapter adapter)

{

PXCache mappingCache = Caches[typeof(SSCustomerMapping)];

// Dictionary to store MappedCustomerID (Acumatica AcctCD) to BAccountID for linking locations

var createdCustomers = new Dictionary<string, int?>();

 

// --- First Pass: Create Main Customer Records (LocationNumber == "0") ---

foreach (SSCustomerMapping row in Mapping.Select().RowCast<SSCustomerMapping>()

.Where(r => r.Selected == true && r.Processed == false && r.LocationNumber == "0" && !string.IsNullOrWhiteSpace(r.MappedCustomerID)))

{

var customerGraph = PXGraph.CreateInstance<CustomerMaint>();

customerGraph.BAccount.View.Answer = WebDialogResult.Yes; // Suppress dialogs

 

// Step 1a: Insert Customer (Business Account)

var customer = customerGraph.BAccount.Insert();

customer.AcctCD = row.MappedCustomerID;

customer.AcctName = row.CustomerName ?? "Unnamed";

customer.CustomerClassID = row.CustomerClass ?? "PAPER";

customer.CuryID = row.Currency ?? "USD";

customer.Status = CustomerStatus.Active;

customer.StatementCycleId = "EOM";

customer.StatementType = "O";

// DiscTakenAcctID and DiscTakenSubID will be defaulted by CustomerMaint graph based on CustomerClassID or AR Setup.

 

// IMPORTANT: Add TermsID, as seen in the working Import Scenario

customer.TermsID = "NET30"; // Provide a default. ENSURE "NET30" exists in your Terms (AR202000)

 

// Update the customer in cache. At this point, customer.BAccountID is usually assigned.

customer = customerGraph.BAccount.Update(customer);

 

// Step 1b: Explicitly create and populate default Contact for the main customer

// Ensure a new Contact DAC instance is created for the default contact

var defaultContact = (Contact)customerGraph.Caches<Contact>().Insert(new Contact());

defaultContact.BAccountID = customer.BAccountID; // Link to the customer

defaultContact.Attention = row.Attention;

defaultContact.Phone1 = row.Phone1;

defaultContact.Phone2 = row.Phone2;

defaultContact.EMail = row.Email;

defaultContact = customerGraph.Caches<Contact>().Update(defaultContact);


 

// Step 1c: Explicitly create and populate default Address for the main customer

// Ensure a new Address DAC instance is created for the default address

var defaultAddress = (Address)customerGraph.Caches<Address>().Insert(new Address());

defaultAddress.BAccountID = customer.BAccountID; // Link to the customer

defaultAddress.AddressLine1 = row.AddressLine1;

defaultAddress.AddressLine2 = row.AddressLine2;

defaultAddress.City = row.City;

defaultAddress.State = row.State;

defaultAddress.PostalCode = row.ZipCode;

defaultAddress.CountryID = row.CountryCode ?? "US";

defaultAddress = customerGraph.Caches<Address>().Update(defaultAddress);

 

// Step 1d: Explicitly create and populate the default Location for the main customer

// Ensure a new Location DAC instance is created for the default location

var defaultLocation = (Location)customerGraph.Caches<Location>().Insert(new Location());

defaultLocation.BAccountID = customer.BAccountID; // Link to the customer

defaultLocation.LocationCD = "MAIN"; // Typically "MAIN" or "0" for the default location

defaultLocation.Descr = row.CustomerName ?? "Main Location"; // Use customer name as description

defaultLocation.IsDefault = true; // Mark as the default location

 

// Now, create a contact and address specifically for this default location

// This is crucial to prevent DefLocationExt from generating invalid IDs

var locationDefaultContact = (Contact)customerGraph.Caches<Contact>().Insert(new Contact());

locationDefaultContact.BAccountID = customer.BAccountID;

locationDefaultContact.Attention = row.Attention; // Copy from customer row

locationDefaultContact.Phone1 = row.Phone1;

locationDefaultContact.EMail = row.Email;

locationDefaultContact = customerGraph.Caches<Contact>().Update(locationDefaultContact);

 

var locationDefaultAddress = (Address)customerGraph.Caches<Address>().Insert(new Address());

locationDefaultAddress.BAccountID = customer.BAccountID;

locationDefaultAddress.AddressLine1 = row.AddressLine1; // Copy from customer row

locationDefaultAddress.AddressLine2 = row.AddressLine2;

locationDefaultAddress.City = row.City;

locationDefaultAddress.State = row.State;

locationDefaultAddress.PostalCode = row.ZipCode;

locationDefaultAddress.CountryID = row.CountryCode ?? "US";

locationDefaultAddress = customerGraph.Caches<Address>().Update(locationDefaultAddress);

 

// Link the default contact and address to the default location

defaultLocation.DefContactID = locationDefaultContact.ContactID;

defaultLocation.DefAddressID = locationDefaultAddress.AddressID;

defaultLocation = customerGraph.Caches<Location>().Update(defaultLocation);


 

// Step 1e: Link the newly created default Contact, Address, and Location to the main customer (BAccount)

customer.DefContactID = defaultContact.ContactID;

customer.DefAddressID = defaultAddress.AddressID;

customer.DefLocationID = defaultLocation.LocationID;

customerGraph.BAccount.Update(customer); // Update customer again in cache to reflect all linked IDs

 

// Step 1f: Save the main customer, its default contact, address, and location

try

{

customerGraph.Save.Press();

PXTrace.WriteInformation($"Successfully created main customer: {customer.AcctCD} (BAccountID: {customer.BAccountID})");

 

// Store the newly created customer's BAccountID for linking additional locations

if (customer.BAccountID.HasValue)

{

createdCustomers[row.MappedCustomerID] = customer.BAccountID;

}

else

{

PXTrace.WriteError($"Failed to get BAccountID for newly created customer: {row.MappedCustomerID}. Locations for this customer may not be created.");

// Do NOT mark as processed if the main customer wasn't created properly

continue;

}

 

// Mark as processed in your mapping table (for this main customer row)

row.Processed = true;

row.IsSynced = true;

row.LastSyncedDateTime = PXTimeZoneInfo.Now;

mappingCache.Update(row);

}

catch (Exception ex)

{

PXTrace.WriteError($"Error saving main customer {customer.AcctCD}: {ex.Message}");

// Do NOT mark as processed if there was an error

}

}

 

// --- Second Pass: Create Additional Location Records (LocationNumber != "0") ---

// Re-selecting to ensure we iterate over records not processed in the first pass

foreach (SSCustomerMapping row in Mapping.Select().RowCast<SSCustomerMapping>()

.Where(r => r.Selected == true && r.Processed == false && r.LocationNumber != "0" && !string.IsNullOrWhiteSpace(r.MappedCustomerID)))

{

// Ensure the parent customer was successfully created in the first pass

if (!createdCustomers.TryGetValue(row.ParentAccountID, out int? parentBAccountID) || !parentBAccountID.HasValue)

{

PXTrace.WriteWarning($"Parent customer with MappedCustomerID '{row.ParentAccountID}' not found for location '{row.MappedCustomerID}'. Skipping location creation.");

continue;

}

 

var customerGraph = PXGraph.CreateInstance<CustomerMaint>();

customerGraph.BAccount.View.Answer = WebDialogResult.Yes; // Suppress dialogs

 

// Load the parent customer into the graph context

// This is important for Acumatica's graph logic to associate the new location correctly

customerGraph.BAccount.Current = customerGraph.BAccount.Search<BAccount.bAccountID>(parentBAccountID);

if (customerGraph.BAccount.Current == null)

{

PXTrace.WriteError($"Could not load parent customer BAccountID '{parentBAccountID}' for location '{row.MappedCustomerID}'. Skipping location creation.");

continue;

}

 

// Step 2a: Create a new Location record

var newLocation = (Location)customerGraph.Caches<Location>().Insert(new Location());

newLocation.BAccountID = parentBAccountID; // Link to the existing parent customer

newLocation.LocationCD = row.LocationNumber;

newLocation.Descr = row.LocationName ?? "Additional Location";

newLocation.IsDefault = false; // This is an *additional* location, not the main default.

// Step 2b: Explicitly create a new Contact for this specific additional location

var newLocationContact = (Contact)customerGraph.Caches<Contact>().Insert(new Contact());

newLocationContact.BAccountID = parentBAccountID; // Link to the same customer

newLocationContact.Attention = row.Attention;

newLocationContact.Phone1 = row.Phone1;

newLocationContact.EMail = row.Email;

newLocationContact = customerGraph.Caches<Contact>().Update(newLocationContact);

 

// Step 2c: Explicitly create a new Address for this specific additional location

var newLocationAddress = (Address)customerGraph.Caches<Address>().Insert(new Address());

newLocationAddress.BAccountID = parentBAccountID; // Link to the same customer

newLocationAddress.AddressLine1 = row.AddressLine1;

newLocationAddress.AddressLine2 = row.AddressLine2;

newLocationAddress.City = row.City;

newLocationAddress.State = row.State;

newLocationAddress.PostalCode = row.ZipCode;

newLocationAddress.CountryID = row.CountryCode ?? "US";

newLocationAddress = customerGraph.Caches<Address>().Update(newLocationAddress);

 

// Step 2d: Link the newly created contact and address to the new location

newLocation.DefContactID = newLocationContact.ContactID;

newLocation.DefAddressID = newLocationAddress.AddressID;

 

// Update the location with its contact and address IDs

customerGraph.Caches<Location>().Update(newLocation);

// Step 2e: Save the additional location and its linked contact/address

try

{

customerGraph.Save.Press();

PXTrace.WriteInformation($"Successfully created location {newLocation.LocationCD} for customer {customerGraph.BAccount.Current?.AcctCD}");

 

// Mark as processed in your mapping table (for this location row)

row.Processed = true;

row.IsSynced = true;

row.LastSyncedDateTime = PXTimeZoneInfo.Now;

mappingCache.Update(row);

}

catch (Exception ex)

{

PXTrace.WriteError($"Error creating location {row.LocationNumber} for customer {row.MappedCustomerID}: {ex.Message}");

// Do NOT mark as processed if there was an error

}

}

 

mappingCache.Graph.Actions.PressSave(); // Save final changes to SSCustomerMapping

return adapter.Get();

}

Best answer by darylbowman

Has anyone successfully created new customers programmatically in Acumatica?

Yes

 

...whether I can default values from a Customer Class

Yes

 

Here is my starting point…

It seems like with a ‘starting point’ that advanced, you’ve been playing with this for a while. I wouldn’t say there are any real ‘gotchas’ with Customers.

1 reply

darylbowman
Captain II
Forum|alt.badge.img+15
  • Answer
  • June 12, 2025

Has anyone successfully created new customers programmatically in Acumatica?

Yes

 

...whether I can default values from a Customer Class

Yes

 

Here is my starting point…

It seems like with a ‘starting point’ that advanced, you’ve been playing with this for a while. I wouldn’t say there are any real ‘gotchas’ with Customers.