Change Document: v2.3.0.0 — CU-60003 Locking Fix, Credential Externalization, and Config-Driven Deployment
| Field | Value |
|---|---|
| Version | 2.3.0.0 |
| Date | 2026-03-06 |
| Extension | BC Dialing Application (Cambay Solutions) |
| Severity | Critical — Root cause of ongoing locking/performance issues |
| Status | Implemented — pending independent review and deployment |
Background
Since the BC Dialing Application was first deployed, CS agents have reported intermittent "information on the page is out of date" errors when editing Customer records, and the Job Queue codeunit (CU-60003) has shown increasing execution times in Application Insights telemetry. Three prior versions (v2.1.0.0, v2.2.0.0, v2.2.1.0) each addressed contributing factors:
- v2.1.0.0 added
Commit()per iteration to break the single long-running transaction into micro-transactions. This was necessary but insufficient — it limited the lock between records but did nothing about the lock duration within each record. - v2.2.0.0 removed
LockTable()on Customer, added secondary indexes, and fixed several data integrity issues with Customer creation. - v2.2.1.0 fixed the permissions gap that caused 65-77% of API calls to fail with HTTP 403 after v2.2.0.0.
Despite these fixes, locking reports continued. A detailed analysis of CU-60003's processing flow revealed the root cause: database writes were interleaved with HTTP I/O within each iteration. The Commit() per iteration released locks between records, but within each record, the first database write (via AddCustomerBlobLink inside SaveSummary) acquired a lock on the Customer record and the Record Link table. All subsequent HTTP calls — SaveRecording's Nextiva API call, MP3 download, Azure Blob upload, then SaveTranscript's API call and upload — ran while holding those locks. Total lock duration per record: 15-40 seconds depending on network latency and file sizes.
This version restructures CU-60003 into a two-phase architecture that separates all I/O from all database writes, reducing lock duration to under 1 second per record. It also addresses several other issues discovered during the analysis: redundant database operations, hardcoded credentials, missing configuration reads, a regression in the Manual Sync path, and incomplete permission sets.
Summary of Changes
| # | Severity | Category | Description |
|---|---|---|---|
| 1 | Critical | Fixed | Restructured CU-60003 into Phase 1 (HTTP/blob I/O) and Phase 2 (DB writes) to eliminate 15-40 second lock windows |
| 2 | High | Fixed | Eliminated 4 redundant Customer lookups per phone log record (4 → 1) |
| 3 | High | Fixed | Eliminated 4 redundant CustomerPhoneLog Modify calls per record (4 → 2) |
| 4 | Medium | Fixed | Page 80004 (EndMailCall) empty trigger — orphaned records with blank Customer No. |
| 5 | High | Fixed | CU-60000 (legacy) missing SetLoadFields, Modify, and Permissions |
| 6 | Critical | Changed | Externalized hardcoded Nextiva credentials to NextivaConfig table |
| 7 | Medium | Changed | Externalized hardcoded BC Base URL to NextivaConfig table |
| 8 | Medium | Changed | Externalized hardcoded Customer Template to NextivaConfig table |
| 9 | Medium | Changed | Externalized hardcoded Azure Blob SAS token to NextivaConfig table |
| 10 | Medium | Fixed | Manual Sync date-based path regression — blobs uploaded but no Record Links or flag updates |
| 11 | Medium | Added | Upgrade codeunit (CU-60005) auto-populates NextivaConfig during deployment |
| 12 | Low | Added | Config API page (80010) for post-deployment verification |
| 13 | Low | Fixed | BCDialerPermissions missing CU-60004, CU-60005, and Page 80010 |
| 14 | Low | Added | Telemetry warnings (BCDIALER-0001 through BCDIALER-0003) on all hardcoded fallback branches for post-deployment config migration validation |
Detailed Changes
1. CU-60003 Phase 1/Phase 2 Architecture (Critical)
Files: src/Codeunit/CU60003.NextivaIntegration.al
Problem: Within each phone log record iteration, the processing flow was:
SaveSummary— HTTP call to Nextiva API → parse response → upload HTML to Azure Blob → write Record Link to Customer (DB WRITE) → update SummaryUpdated flag (DB WRITE)SaveRecording— HTTP call to Nextiva API → download MP3 → upload to Azure Blob → write Record Link (DB WRITE) → update RecordingUpdated flag (DB WRITE) ← all while holding locks from step 1SaveTranscript— HTTP call to Nextiva API → parse transcript → upload HTML to Azure Blob → write Record Link (DB WRITE) → update TranscriptUpdated flag (DB WRITE) ← all while holding locks from steps 1 and 2
The first RecRef.AddLink() in SaveSummary acquired a write lock on the Record Link system table and a read lock on the Customer record. Every subsequent HTTP call, blob upload, and DB write in SaveRecording and SaveTranscript ran under those locks. Network latency, Nextiva API response times, and Azure Blob upload times all contributed to the lock window — 15-40 seconds observed in telemetry.
Fix — Phase 1/Phase 2 separation:
Old Save* procedures were split into:
-
Phase 1 (FetchAndUpload):* All HTTP calls and Azure Blob uploads. No database writes. Returns blob URLs in
varparameters.FetchAndUploadSummary— Nextiva API call + HTML upload → returnsSummaryUrlFetchAndUploadRecording— Nextiva API call + MP3 download + upload → returnsRecordingUrlFetchAndUploadTranscript— Nextiva API call + HTML upload → returnsTranscriptUrlFetchAndUploadInboundEmail/FetchAndUploadOutboundEmail— same pattern for emails
-
Phase 2 (DB writes only): All database writes in a tight batch after all I/O is complete.
WriteBlobLinksToCustomer— single Customer Get by primary key → up to 3RecRef.AddLink()callsWriteEmailLinksToCustomer— same pattern for email linksUpdateAllPhoneLogFlags— single Get + single Modify that sets all three boolean flagsUpdateAllEmailLogFlags— single Get + single Modify for email flags
The restructured TryProcessPhoneLog flow:
// Single Customer lookup
CustomerRec.SetLoadFields("No.", "Phone No.");
CustomerRec.SetRange("Phone No.", ProcessCalls.PhoneNumber);
CustomerRec.FindFirst();
// Phase 1: All HTTP + blob I/O (no DB locks held)
TempNextiva := NextivaLib.FetchAndUploadSummary(Domain, Token, WorkItemID, No, SummaryUrl);
TempNextivaOut := NextivaLib.FetchAndUploadRecording(Domain, Token, TempNextiva, No, RecordingUrl);
NextivaLib.FetchAndUploadTranscript(Domain, Token, TempNextivaOut, No, TranscriptUrl);
// Phase 2: All DB writes in <1 second
NextivaLib.WriteBlobLinksToCustomer(CustomerRec."No.", SummaryUrl, RecordingUrl, TranscriptUrl, Date, Time);
NextivaLib.UpdateAllPhoneLogFlags(No, SummaryUrl <> '', RecordingUrl <> '', TranscriptUrl <> '');
Impact: Lock duration reduced from 15-40 seconds to under 1 second per record. The Commit() per iteration (added in v2.1.0.0) continues to release locks between records.
2. Redundant Customer Lookups Eliminated (High)
Files: src/Codeunit/CU60003.NextivaIntegration.al
Problem: Each of SaveSummary, SaveRecording, SaveTranscript, and AddCustomerBlobLink contained its own Customer.SetRange("Phone No.", ...) + FindFirst() call. Four database round-trips per phone log record to find the same Customer.
Fix: Single Customer lookup in TryProcessPhoneLog at the start of processing. The Customer "No." (primary key) is passed to WriteBlobLinksToCustomer which uses Customer.Get(CustomerNo) — a primary key lookup that's effectively free.
3. Redundant PhoneLog Modifies Eliminated (High)
Files: src/Codeunit/CU60003.NextivaIntegration.al
Problem: The old UpdateNextiveEntryFlags procedure was called 3 times (once per asset type: Summary, Recording, Transcript), each performing a Get + Modify to set one boolean flag. Additionally, UpdateAttempt performed a 4th Get + Modify to increment the attempt counter. Total: 4 separate Get+Modify pairs per record.
Fix: New UpdateAllPhoneLogFlags procedure performs a single Get and sets all three boolean flags in one Modify call. UpdateAttempt is called separately (it increments regardless of success/failure). Total: 2 Get+Modify pairs (flags + attempt) instead of 4.
4. Page 80004 EndMailCall — Empty Trigger (Medium)
Files: src/Page/Pag-80004.EndMailCall.al
Problem: The OnInsertRecord trigger was empty and SourceTableTemporary was commented out. Every email end-of-call event created a CustomerEmailLog record with a blank Customer No. field — the record couldn't be traced back to a Customer without re-querying by email address.
Fix: Added Customer email lookup in OnInsertRecord matching the pattern used by EndCall (Page 80001):
trigger OnInsertRecord(BelowxRec: Boolean): Boolean
var
cust: Record Customer;
begin
cust.SetLoadFields("No.", "E-Mail");
cust.SetRange("E-Mail", Rec.EmailAddress1);
if cust.FindFirst() then
Rec."Customer No." := cust."No.";
end;
Removed all commented-out dead code. SourceTableTemporary left as false (matching EndCall behavior).
5. CU-60000 Legacy PhoneIntegration Fixes (High)
Files: src/Codeunit/CU-60000.PhoneIntegration.al
Problem: This legacy codeunit (called by Page 60000) had the same bugs that were fixed in Pages 80000/80003 in earlier versions:
- No
SetLoadFieldson Customer lookup (full 200+ field load for a phone number check) - No
Modify(true)afterApplyCustomerTemplate— the phone number set before template application was overwritten by template defaults - No inline
Permissions— same 403 failure pattern as pre-v2.2.1.0
Fix: Added SetLoadFields("No.", "Phone No.") with separate lookup/write variables, added Modify(true) after template application to preserve the phone number, and added inline Permissions for all 13 standard BC tables plus NextivaConfig. Now reads BC Base URL from NextivaConfig with hardcoded fallback.
6. Credential Externalization (Critical — Security)
Files: src/Codeunit/CU60003.NextivaIntegration.al, src/Codeunit/CU-60004.NextivaIntegrationManual.al
Problem: Nextiva API credentials (Login URL and AuthText) were hardcoded as string constants in both codeunits. This meant credentials were embedded in the compiled .app package, visible in version control, and required a code change + redeployment to rotate.
Fix: CU-60003 reads NextivaConfig in OnRun and passes the Config record to GetDomain and GetNextivaToken. CU-60004 uses a LoadConfig() helper that reads from NextivaConfig on first call. All hardcoded credential assignments removed.
7. BC Base URL Externalization (Medium)
Files: src/Page/Pag-80000.ReceivePhoneNumber.al, src/Page/Pag-80003.ReceiveEmail.al, src/Codeunit/CU-60000.PhoneIntegration.al
Problem: The BC Base URL (used to generate CustomerURL values pointing to the Customer Card) was hardcoded with the production tenant GUID and company name. This URL would break if the environment or tenant changed.
Fix: All three consumers now read Config."BC Base URL" from the NextivaConfig table with the hardcoded URL as a fallback for backward compatibility.
8. Customer Template Externalization (Medium)
Files: src/Page/Pag-80000.ReceivePhoneNumber.al, src/Page/Pag-80003.ReceiveEmail.al
Problem: The customer template name 'ECOMMERCE' was hardcoded in both API pages. If the template name changed or different templates were needed for different customer types, a code change was required.
Fix: Both pages now read Config."Customer Template" from NextivaConfig with 'ECOMMERCE' as the fallback. The Config record is already loaded for the BC Base URL read, so this adds zero additional database calls.
9. Azure Blob SAS Token Externalization (Medium)
Files: src/Table/Tab80003.NextivaConfig.al, src/Page/Pag-80007.NextivaSetupPage.al, src/Codeunit/CU60003.NextivaIntegration.al, src/Codeunit/CU-60004.NextivaIntegrationManual.al, src/Codeunit/CU-60005.DialerUpgrade.al, src/Page/Pag-80010.NextivaConfigAPI.al
Problem: The Azure Blob Storage SAS token was hardcoded in TryInitializeAuth in both CU-60003 and CU-60004. SAS tokens have expiration dates and must be rotated periodically — a hardcoded token requires a code change and redeployment to rotate.
Fix: Added "Azure Blob SAS Token" field (Text[500]) to the NextivaConfig table. TryInitializeAuth in both codeunits now reads from config with the hardcoded token as fallback. The upgrade codeunit populates the field during deployment. The setup page exposes it with ExtendedDatatype = Masked for security. The API page reports a boolean azureBlobSASTokenConfigured (never exposes the raw token).
Performance impact: Zero. TryInitializeAuth calls Config.Get('DEFAULT') which is a single-record primary key lookup. The NextivaConfig table has exactly one record. BC's server-side data cache likely serves this from memory after the first read in OnRun.
10. Manual Sync Date-Based Path Regression (Medium)
Files: src/Page/Pag-80009.NextivaManualSync.al, src/Codeunit/CU60003.NextivaIntegration.al
Problem: When CU-60003 was restructured for Phase 1/Phase 2, the old Save* procedures were converted to legacy stubs that only called Phase 1 (FetchAndUpload*). Phase 2 (WriteBlobLinksToCustomer + UpdateFlags) was handled in TryProcessPhoneLog — but the Manual Sync page's date-based code path called the old Save* stubs directly, bypassing Phase 2. Result: blobs were uploaded to Azure but no Record Links were written to the Customer record and no processing flags were updated. Records appeared unprocessed after manual sync.
Fix: Rewrote Pag-80009's date-based path to call Phase 1 procedures directly (FetchAndUploadSummary, FetchAndUploadRecording, FetchAndUploadTranscript), followed by Phase 2 (WriteBlobLinksToCustomer + UpdateAllPhoneLogFlags). Same pattern for emails. Added Permissions = tabledata Customer = R for the Customer lookup. Removed the legacy Save* stubs from CU-60003 (dead code after this fix).
The WorkItem-based path continues to use CU-60004's own Save* implementations, which have the original interleaved pattern. This is acceptable because:
- WorkItem sync is user-initiated (not Job Queue)
- It processes a single record at a time
- The interleaved pattern's lock duration is acceptable for single-record manual operations
11. Upgrade Codeunit (Medium)
Files: src/Codeunit/CU-60005.DialerUpgrade.al (NEW)
Added Subtype = Upgrade codeunit that runs OnUpgradePerCompany when the extension version crosses 2.3.0.0. The PopulateNextivaConfig procedure:
- If no config record exists: creates one with all default values (Login URL, AuthText, BC Base URL, Customer Template, Azure Blob SAS Token)
- If a config record exists: fills in any empty fields without overwriting manual entries
This eliminates the manual pre-deployment configuration step that was required before v2.3.0.0.
12. Config API Page (Low)
Files: src/Page/Pag-80010.NextivaConfigAPI.al (NEW)
Read-only API page for verifying config population after deployment. Endpoint: GET /api/CambaySolutions/PhoneIntegration/v1.0/nextivaConfigs. Exposes:
primaryKey,bcBaseURL,customerTemplate,nextivaLoginURLnextivaAuthTextConfigured(boolean — true if AuthText is non-empty)azureBlobSASTokenConfigured(boolean — true if SAS token is non-empty)
Credentials are never exposed over the wire — only boolean indicators.
13. Permission Set Update (Low)
Files: BCDialerPermissions.permissionset.al
Added missing entries:
codeunit "Nextiva Integration Manual" = Xcodeunit "Dialer Upgrade" = Xpage NextivaConfigAPI = X
Object Inventory
| Object ID | Object Type | Name | Status |
|---|---|---|---|
| 60000 | Codeunit | PhoneIntegration | Modified — SetLoadFields, Modify(true), inline Permissions, config-driven URL |
| 60003 | Codeunit | Nextiva Integration | Modified — Phase 1/Phase 2 restructure, config-driven credentials and SAS token, legacy stubs removed |
| 60004 | Codeunit | Nextiva Integration Manual | Modified — LoadConfig helper, config-driven credentials and SAS token |
| 60005 | Codeunit | Dialer Upgrade | New — upgrade codeunit for NextivaConfig population |
| 60000 | PermissionSet | BCDialerPermissions | Modified — added CU-60004, CU-60005, Page 80010 |
| 80000 | Page | ReceivePhoneNumber | Modified — config-driven URL and template |
| 80003 | Page | ReceiveEmail | Modified — config-driven URL and template |
| 80004 | Page | EndMailCall | Modified — Customer lookup in OnInsertRecord |
| 80007 | Page | NextivaSetup | Modified — Azure Blob SAS Token field added |
| 80009 | Page | Nextiva Manual Sync | Modified — date-based path uses Phase 1+Phase 2, Customer permission added |
| 80010 | Page | NextivaConfigAPI | New — read-only config verification API |
| 80003 | Table | NextivaConfig | Modified — Azure Blob SAS Token field added |
| — | app.json | Extension manifest | Modified — version bump to 2.3.0.0 |
File Structure
Cambay Solutions_BC Dialing Application/
├── app.json
├── BCDialerPermissions.permissionset.al
├── docs/
│ ├── CHANGELOG.md
│ ├── CHANGE-v2.3.0.0.md ← this document
│ ├── CHANGE-v2.2.1.0.md
│ ├── CHANGE-v2.2.0.0.md
│ └── CHANGE-v2.1.0.0.md
├── src/
│ ├── Codeunit/
│ │ ├── CU-60000.PhoneIntegration.al
│ │ ├── CU-60001.AttachMediaCodeunit.al
│ │ ├── CU-60002.AzureBlobStorage.al
│ │ ├── CU60003.NextivaIntegration.al
│ │ ├── CU-60004.NextivaIntegrationManual.al
│ │ └── CU-60005.DialerUpgrade.al ← new
│ ├── Page/
│ │ ├── Pag-60000.ReceivePhoneNumber.al
│ │ ├── Pag-60001.CompletionComponent.al
│ │ ├── Pag-60002.CallLogList.al
│ │ ├── Pag-60009.AzureStorageConfiguration.al
│ │ ├── Pag-80000.ReceivePhoneNumber.al
│ │ ├── Pag-80001.EndCall.al
│ │ ├── Pag-80003.ReceiveEmail.al
│ │ ├── Pag-80004.EndMailCall.al
│ │ ├── Pag-80005.CustomerPhoneLogViewer.al
│ │ ├── Pag-80006.CustomerEmailLogViewer.al
│ │ ├── Pag-80007.NextivaSetupPage.al
│ │ ├── Pag-80008.NextivaAPIViewer.al
│ │ ├── Pag-80009.NextivaManualSync.al
│ │ ├── Pag-80010.NextivaConfigAPI.al ← new
│ │ └── Pag-80011.NextivaErrorLogsViewer.al
│ ├── Table/
│ │ ├── Tab60001.AzureStorageConfig.al
│ │ ├── Tab80000.CustomerPhoneLog.al
│ │ ├── Tab80001.NextivaData.al
│ │ ├── Tab80002.CustomerEmailLog.al
│ │ ├── Tab80003.NextivaConfig.al
│ │ └── Tab80004.NextivaErrorLogs.al
│ └── TableExt/
│ └── TabExt-60000.CustomerDialerKeys.al
├── tools/
│ ├── verify_deployment.py ← new (deployment verification)
│ └── requirements.txt ← new
└── README.md
Credential Migration
What Changed
Prior to v2.3.0.0, all integration credentials and environment-specific values were hardcoded as string constants in AL codeunits and pages:
- Nextiva Login URL (
https://login.thrio.com) — in CU-60003, CU-60004 - Nextiva AuthText (
api.admin+...@nextiva.com:MRhsP5Q1EE3g3Go) — in CU-60003, CU-60004 - BC Base URL (with production tenant GUID) — in Pag-80000, Pag-80003, CU-60000
- Customer Template (
ECOMMERCE) — in Pag-80000, Pag-80003, CU-60000 - Azure Blob SAS Token — in CU-60003, CU-60004
- Azure Storage Account (
p360data) and Container Name (nextiva-bc) — in CU-60003, CU-60004
These values were embedded in the compiled .app package, visible in version control, and required a code change and redeployment to rotate or update.
How It Works Now
v2.3.0.0 moves the first five values to the NextivaConfig table (80003), which has a single record with Primary Key 'DEFAULT'. All consumers read from this table at runtime:
| Value | Config Field | Read By | Fallback |
|---|---|---|---|
| Nextiva Login URL | "Nextiva Login URL" | CU-60003 OnRun, CU-60004 LoadConfig | Error if empty |
| Nextiva AuthText | "Nextiva AuthText" | CU-60003 OnRun, CU-60004 LoadConfig | Error if empty |
| BC Base URL | "BC Base URL" | Pag-80000, Pag-80003, CU-60000 | Hardcoded production URL |
| Customer Template | "Customer Template" | Pag-80000, Pag-80003 | 'ECOMMERCE' |
| Azure Blob SAS Token | "Azure Blob SAS Token" | CU-60003 TryInitializeAuth, CU-60004 TryInitializeAuth | Hardcoded SAS token |
The upgrade codeunit (CU-60005) automatically populates the config record during deployment, using the same values that were previously hardcoded. This means the v2.3.0.0 deployment is zero-touch — no manual configuration is required.
Hardcoded Fallbacks Still in the Code
v2.3.0.0 retains hardcoded fallback values in two categories:
Category 1 — Upgrade codeunit (CU-60005): Contains all five credential/URL values as seed data for the config record. These are intentional — the upgrade codeunit needs the values to populate the config table during the initial migration. After v2.3.0.0 is deployed and the config is populated, the upgrade codeunit's PopulateNextivaConfig procedure will not run again (it's gated by ModInfo.DataVersion() < Version.Create(2, 3, 0, 0)).
Category 2 — Runtime fallbacks with telemetry: Several consumers have else branches that fall back to hardcoded values when the config field is empty. Each fallback branch emits a Session.LogMessage warning to Application Insights, making fallback activation immediately visible in telemetry without breaking the integration. The telemetry events use event IDs BCDIALER-0001 (BC Base URL), BCDIALER-0002 (Customer Template), and BCDIALER-0003 (Azure Blob SAS Token), with a ConfigField custom dimension identifying the specific field.
To detect fallback activation after deployment, query Application Insights:
traces
| where message has "BCDIALER-000"
| where severityLevel == 2 // Warning
| project timestamp, message, customDimensions.ConfigField
| order by timestamp desc
If no results appear, all config values are being read from the NextivaConfig table as expected.
| File | Fallback Value | Fallback Condition | Telemetry Event |
|---|---|---|---|
Pag-80000.ReceivePhoneNumber.al | Production BC Base URL | Config."BC Base URL" = '' | BCDIALER-0001 |
Pag-80000.ReceivePhoneNumber.al | 'ECOMMERCE' | Config."Customer Template" = '' | BCDIALER-0002 |
Pag-80003.ReceiveEmail.al | Production BC Base URL | Config."BC Base URL" = '' | BCDIALER-0001 |
Pag-80003.ReceiveEmail.al | 'ECOMMERCE' | Config."Customer Template" = '' | BCDIALER-0002 |
CU-60000.PhoneIntegration.al | Production BC Base URL | Config."BC Base URL" = '' | BCDIALER-0001 |
CU-60000.PhoneIntegration.al | 'ECOMMERCE' | Config not read (legacy) | N/A (no config read) |
CU60003.NextivaIntegration.al | Hardcoded SAS token | Config."Azure Blob SAS Token" = '' | BCDIALER-0003 |
CU-60004.NextivaIntegrationManual.al | Hardcoded SAS token | Config."Azure Blob SAS Token" = '' | BCDIALER-0003 |
Category 3 — Not yet externalized: The Azure Storage Account name (p360data) and Container Name (nextiva-bc) remain hardcoded in UploadToBlob and UploadMP3ToBlob in CU-60003 (lines 686, 754) and CU-60004 (lines 289, 718). These were not added to NextivaConfig in v2.3.0.0 to limit the scope of the schema change.
Follow-Up: Remove Hardcoded Fallbacks (v2.4.0.0)
After v2.3.0.0 is deployed and the config table is confirmed populated, a follow-up version should:
-
Remove all Category 2 runtime fallbacks. Replace
elsebranches withError()calls that fail fast if config is missing, rather than silently falling back to hardcoded values. This ensures config issues are caught immediately rather than masked by stale fallback data. -
Externalize Category 3 values. Add
"Azure Storage Account"and"Azure Container Name"fields to NextivaConfig and read them inUploadToBlob/UploadMP3ToBlobin CU-60003 and CU-60004 (4 locations each). Add"Email Domain Filter"field for the@bestwayusa.comcheck inSaveOutboundEmailin CU-60003 (line 686) and CU-60004 (line 914). Update the upgrade codeunit to populate these fields. -
Remove credentials from CU-60005 upgrade codeunit. After v2.3.0.0 is deployed, the version gate (
DataVersion < 2.3.0.0) preventsPopulateNextivaConfigfrom running again. However, the credentials remain in the compiled.appas dead code — including the Nextiva API credential in plaintext. In v2.4.0.0, bump the version gate to< 2.4.0.0and replace the seed values with empty strings or remove the procedure entirely. Any future config changes should be made via the Nextiva Config Setup page (80007), not the upgrade codeunit. -
Migrate
UseReadySAStoSecretTextparameter type. This addresses the AL0432 deprecation warning and prevents the SAS token from appearing in runtime logs. -
Add BC Base URL validation. Add a runtime check in Pages 80000, 80003, and CU-60000 that warns (via telemetry) if the BC Base URL read from config does not contain
%1. If%1is missing, theStrSubstNosubstitution produces a URL without a customer filter, breaking the Nextiva screen pop. Also add a validation warning on the Nextiva Config Setup page (80007) to flag this at configuration time. -
Fix CU-60005 upgrade logic. Change the
BC Base URLpopulation check from= ''(empty-only) to also verify the existing value contains%1. If the stored URL is non-empty but missing%1, overwrite it with the correct URL. This prevents the screen pop misconfiguration that occurred during the v2.3.0.0 deployment when a pre-existing config record had an incomplete URL.
These changes are low-risk and mechanical but should not be bundled with v2.3.0.0. The priority for v2.3.0.0 is resolving the locking issue. The fallback removal is a hardening step that should happen after the config-driven approach is validated in production.
Known Limitations
CU-60004 Manual Sync Retains Interleaved Pattern (Accepted)
CU-60004 (Nextiva Integration Manual) still uses the original interleaved DB-writes-during-HTTP pattern in its own SaveSummary/SaveRecording/SaveTranscript implementations. This was not restructured because:
- CU-60004 is only called from the Manual Sync page (Pag-80009) via the WorkItem-based path
- It processes a single record at a time (user-specified WorkItem ID)
- It's user-initiated, not Job Queue — there's no concurrent processing pressure
- The lock duration (15-40 seconds) is acceptable for a manual admin operation
If Manual Sync is ever extended to batch processing or automated scheduling, CU-60004 should be restructured to match CU-60003's Phase 1/Phase 2 pattern.
UseReadySAS Deprecation Warning (Accepted)
Both CU-60003 and CU-60004 generate AL0432 warnings: Method 'UseReadySAS' is marked for removal. Use UseReadySAS with SecretText data type for SASToken. This requires migrating the SAS token parameter from Text to SecretText, which is a broader change involving the TryInitializeAuth signature and callers. Deferred to a future version.
Hardcoded Credential Fallbacks Still Present (Transitional — Remove in v2.4.0.0)
v2.3.0.0 reads all credentials and environment-specific values from the NextivaConfig table, but retains hardcoded fallback values in else branches for backward compatibility during the migration. These fallbacks mask configuration issues by silently using stale values instead of failing visibly. After v2.3.0.0 is deployed and the config is confirmed working in production, the fallbacks should be replaced with Error() calls in v2.4.0.0. See the Credential Migration section above for the complete inventory and remediation plan.
Azure Storage Account, Container Name, and Email Domain Not Yet Externalized (Deferred)
The Azure Storage Account (p360data), Container Name (nextiva-bc), and email domain filter (@bestwayusa.com) remain hardcoded in CU-60003 and CU-60004. Adding these to NextivaConfig was deferred to limit the scope of v2.3.0.0. See the Credential Migration section for the v2.4.0.0 plan.
BC Base URL Upgrade Logic Does Not Validate Existing Values (Fixed via Config — Code Fix in v2.4.0.0)
The upgrade codeunit (CU-60005) only populates the BC Base URL field if it is empty. During the v2.3.0.0 deployment, a pre-existing config record contained an incomplete BC Base URL (missing &page=21&filter='No.' IS '%1'), which caused the Nextiva screen pop to open BC without filtering to a specific customer. This was resolved by manually updating the config value in Production on March 6, 2026. The v2.4.0.0 upgrade codeunit will validate that the existing URL contains the %1 placeholder and overwrite it if missing. A runtime validation warning will also be added to Pages 80000, 80003, and CU-60000.
No. Series Line Contention (Accepted — Unchanged from v2.2.1.0)
Concurrent Customer creation on Pages 80000 and 80003 can serialize on the No. Series Line row lock. Observed rate: 0.7% of requests. See CHANGE-v2.2.1.0.md for full analysis.
Deployment Notes
Pre-Deployment
- No manual configuration required. The upgrade codeunit (CU-60005) automatically populates NextivaConfig with default values during deployment.
- No data migration needed.
- No new dependencies.
Deployment
- Build the
.apppackage via AL: Package in VS Code - Deploy to the target environment via Extension Management or the admin center
- The upgrade codeunit runs automatically during installation
Post-Deployment Verification
Automated Verification Script
A Python deployment verification script is included at tools/verify_deployment.py. It tests each integration point end-to-end:
# Step 1: Verify config population (immediately after deployment)
cd "Cambay Solutions_BC Dialing Application/tools"
../../tools/venv/bin/python verify_deployment.py --config-only
# Step 2: Full validation including external connectivity
../../tools/venv/bin/python verify_deployment.py \
--nextiva-auth-text "api.admin+4189575@nextiva.com:MRhsP5Q1EE3g3Go" \
--azure-sas-token "si=BCDialer&spr=https&sv=2024-11-04&sr=c&sig=C%2Bess2NAjBzma9Jt0ejzuVrK1YLEI5xZO%2F63o1YkLcA%3D"
# Step 3: Point at Production
../../tools/venv/bin/python verify_deployment.py --environment Production \
--nextiva-auth-text "..." --azure-sas-token "..."
The script requires a Python 3.13 venv with requests, python-dotenv, and azure-storage-blob. The shared venv is at tools/venv/ in the project root. BC OAuth2 credentials are read from the .env file.
| Check | What it validates | How |
|---|---|---|
| Config API | NextivaConfig record exists with all fields populated | GET /api/CambaySolutions/PhoneIntegration/v1.0/nextivaConfigs |
| Nextiva Auth | Configured Login URL + AuthText authenticate successfully | Calls login.thrio.com/provider/token-with-authorities |
| Azure Blob | SAS token is valid and container is accessible | Lists blobs in p360data/nextiva-bc |
| Customer Template | Configured template exists in BC | Queries standard BC customerTemplates API |
Credentials are passed as CLI arguments rather than read from the Config API because the API intentionally does not expose raw secrets over the wire. The script is run by authorized personnel with the credentials at hand.
Manual Verification Steps
-
Verify config population via OData:
GET /api/CambaySolutions/PhoneIntegration/v1.0/nextivaConfigsExpected response: single record with
primaryKey = "DEFAULT", all URL/template fields populated,nextivaAuthTextConfigured = true,azureBlobSASTokenConfigured = true. -
Verify via Nextiva Config Setup page: Navigate to the setup page (80007) and confirm all 6 fields are populated (BC Base URL, Customer Template, Nextiva Login URL, Nextiva AuthText, Azure Blob SAS Token).
-
Monitor Application Insights for the first hour:
- CU-60003 execution time should drop significantly (target: under 5 seconds per run vs. 12 seconds–4 minutes previously)
- HTTP 409 (SQL lock timeout) rate should decrease
- No new 403 errors on any API page
-
Test Manual Sync with a known WorkItem ID to verify Record Links are written and flags are updated.
-
Test date-based Manual Sync with a recent date to verify the restructured code path works end-to-end.
-
Verify no hardcoded fallbacks activated: After the first few Job Queue cycles, check the Nextiva Error Logs (Page 80008) for any new errors. If the config-driven code paths are working, there should be no errors related to authentication or blob upload failures.
Testing
Refer to the UAT Test Plan for comprehensive test scenarios. Key scenarios for this version:
| # | Scenario | Expected Result |
|---|---|---|
| 1 | Job Queue processes pending phone log | All three assets (summary, recording, transcript) uploaded to Azure Blob. Record Links appear on Customer Card. Processing flags set to true. Execution time under 5 seconds. |
| 2 | Job Queue processes pending email log | Inbound and outbound emails uploaded. Record Links appear on Customer Card. Flags updated. |
| 3 | CS agent edits Customer Card during Job Queue processing | No "out of date" error. Save completes successfully. |
| 4 | Manual Sync — WorkItem ID | Single record processed via CU-60004. Blobs uploaded, Record Links written, flags updated. |
| 5 | Manual Sync — Date range | Records processed via CU-60003 Phase 1+Phase 2. Blobs uploaded, Record Links written, flags updated. |
| 6 | Incoming call with unknown phone number | New Customer created with template from config (or 'ECOMMERCE' fallback). CustomerURL uses BC Base URL from config. |
| 7 | Config API returns correct data | OData endpoint returns config record with correct values. AuthText and SAS token shown as boolean (never raw values). |
| 8 | Fresh deployment with no prior config | Upgrade codeunit creates DEFAULT record with all default values. No Error on first Job Queue run. |
| 9 | Deployment with existing manual config | Existing values preserved. Only empty fields filled by upgrade codeunit. |