Plan Modification

How to follow this chapter

Inputs: An existing eligible portfolio, its latest state and the newly selected proposal.

Steps and expected result: Choose the proposal and prepare the contract before submission. Keep the modification order ID and track it as described in Plan creation.

Alternative or later actions: Check HasPendingOrder and the API result before allowing another change; the service remains authoritative for eligibility.

DTO source: PortfolioOrderOutputModel, PortfolioOutputModel, ProposalOutputModel, ReasonToChangeProposalOutputModel

Variables assigned in an earlier block are reused by later blocks. Client-entered values come from your UI; keep identifiers and lookup results in the current client context. Do not create a fresh context between steps.

The snippets use shared helper classes. See Shared helper classes: complete C# and Java source before copying an example.
Before you start: A selected portfolioId, an authorized CLIENT or ADMIN token and no pending order. Check the current portfolio and choose an alternative proposal.
Keep for the next step: A new modification order. Track its status before allowing another change.
Clients can request a different investment model when the product, portfolio lifecycle and pending-order state allow it. Keep in mind that if the risk associated with the new portfolio model does not match the client's risk profile, you must provide the reason to change proposal response ID(s).

Attention

Check the current lifecycle and pending-order state before offering a plan modification. The server remains authoritative for eligibility.

Selecting proposal

Inputs: The latest portfolio snapshot and client selection.
Action: Resolve the proposal permitted for the requested change.
Keep / expected result: Keep selectedProposalId and any required deviation reasons.
Retrieve the current snapshot and proposals for the portfolio's investment category. Let the client choose selectedProposalId from alternativeProposals and collect selectedReasonIds when required. These are client decisions; do not automatically choose the first proposal or reason.
								
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var clientPortfolio = await httpClient.GetFromJsonAsync<PortfolioOutputModel>($"api/v1/user-portfolios/portfolio-id/{portfolioId}?portfolio-view-as=Snapshot");
var url = $"api/v1/proposals/investment-category-id/{clientPortfolio.InvestmentCategoryId}";
var proposals = await httpClient.GetFromJsonAsync<List<ProposalOutputModel>>(url);
var alternativeProposals = proposals.Where(x => x.Id != clientPortfolio.ProposalId).ToList();

									
								
HttpRequest portfolioRequest = HttpRequest.newBuilder()
    .timeout(Duration.ofMinutes(1))
    .uri(Settings.wmsUri("user-portfolios/portfolio-id/" + portfolioId + "?portfolio-view-as=Snapshot"))
    .header("Accept-Language", "de-DE")
    .header("Authorization", "Bearer " + accessToken)
    .header("Content-Type", "application/json")
    .build();

var response = httpClient.send(portfolioRequest, BodyHandlers.ofString());
Api.requireSuccess(response);
PortfolioOutputModel clientPortfolio = new Gson().fromJson(response.body(), PortfolioOutputModel.class);

// Alternative proposals list
HttpRequest proposalsRequest = HttpRequest.newBuilder()
    .timeout(Duration.ofMinutes(1))
    .uri(Settings.wmsUri("proposals/investment-category-id/" + clientPortfolio.InvestmentCategoryId))
    .header("Accept-Language", "de-DE")
    .header("Authorization", "Bearer " + accessToken)
    .header("Content-Type", "application/json")
    .build();

response = httpClient.send(proposalsRequest, BodyHandlers.ofString());
Api.requireSuccess(response);
Type listType = new TypeToken<ArrayList<ProposalOutputModel>>(){}.getType();
List<ProposalOutputModel> proposalObjs = new Gson().fromJson(response.body(), listType);

var alternativeProposals = proposalObjs.stream().filter(x -> x.Id != clientPortfolio.ProposalId).toList();
// selectedProposalId is chosen by the client from alternativeProposals.

Preparing the contract

Inputs: The selected proposal and client details.
Action: Prepare the contract for the strategy change.
Keep / expected result: Keep the PDF bytes or contractPath, as in Plan creation.
Prepare a new contract for selectedProposalId; do not reuse the contract from the original opening order. Follow Preparing the contract with the current client details and the new proposal in formFields. Set contractPath to the completed PDF that this chapter uploads. In C#, if the optional PDF service returned pdfContractAsByteArray, save those bytes to a separate completed-contract file and use that path; do not upload the blank template.

Submitting the order

Inputs: portfolioId, selectedProposalId, deviation reasons and PDF contract.
Action: Submit the modification order.
Keep / expected result: Keep modificationOrderId and track the order before treating the change as complete.
In the following code snippet, we submit a plan change, passing the new Proposal ID selected by the user/client. The API records a modification order; the strategy changes after the downstream workflow processes it.
								
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Please note:
//   Provide the completed contract required for this modification.
//   The client chooses selectedProposalId and selectedReasonIds.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
var currentPortfolio = await httpClient.GetFromJsonAsync<PortfolioOutputModel>($"api/v1/user-portfolios/portfolio-id/{portfolioId}?portfolio-view-as=Snapshot");
if (currentPortfolio.HasPendingOrder)
{
    throw new InvalidOperationException("A portfolio order is pending");
}
var reasonToChangeProposalUrl = $"api/v1/reason-to-change-proposed-proposal/investment-category-id/{currentPortfolio.InvestmentCategoryId}/active";
var reasonToChangeProposal = await httpClient.GetFromJsonAsync<ReasonToChangeProposalOutputModel>(reasonToChangeProposalUrl);
var orderPortfolioModificationInputModel = new
{
    PortfolioId = portfolioId,
    // Assume this is the new portfolio model selected by client
    ProposalId = selectedProposalId,
    // Assume this is reason to change proposal selected by client
    ReasonToChangeProposalResponsesIds = selectedReasonIds
};
// contractPath points to the completed contract for this modification.
var byteArray = await File.ReadAllBytesAsync(contractPath);
using var httpContent = GuideHttp.CreateMultipartContent(orderPortfolioModificationInputModel, byteArray);
var httpResponseMessage = await httpClient.PostAsync("api/v1/user-portfolio-orders/modification", httpContent);
httpResponseMessage.EnsureSuccessStatusCode();
var modificationOrder = JsonConvert.DeserializeObject<PortfolioOrderOutputModel>(await httpResponseMessage.Content.ReadAsStringAsync());
var modificationOrderId = modificationOrder.Id;

									
								
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Please note:
//   Provide the completed contract required for this modification.
//   The client chooses selectedProposalId and selectedReasonIds.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

HttpRequest reasonToChangeRequest = HttpRequest.newBuilder()
    .timeout(Duration.ofMinutes(1))
    .uri(Settings.wmsUri("reason-to-change-proposed-proposal/investment-category-id/" + clientPortfolio.InvestmentCategoryId + "/active"))
    .header("Accept-Language", "de-DE")
    .header("Authorization", "Bearer " + accessToken)
    .header("Content-Type", "application/json")
    .build();

response = httpClient.send(reasonToChangeRequest, BodyHandlers.ofString());
Api.requireSuccess(response);
ReasonToChangeProposalOutputModel reasonToChangeProposalObj = new Gson().fromJson(response.body(), ReasonToChangeProposalOutputModel.class);
// selectedReasonIds contains the reasons selected by the client.

JsonObject orderPortfolioModificationInputModel = new JsonObject();
orderPortfolioModificationInputModel.addProperty("PortfolioId", portfolioId);
orderPortfolioModificationInputModel.addProperty("ProposalId", selectedProposalId);
orderPortfolioModificationInputModel.add("ReasonToChangeProposalResponsesIds", new Gson().toJsonTree(selectedReasonIds));

// contractPath points to the completed contract for this modification.
var contractBytes = Files.readAllBytes(contractPath);

MultiPartBodyPublisher multipartBody = new MultiPartBodyPublisher()
    .addPart("jsonPayload", orderPortfolioModificationInputModel.toString())
    .addPart("userContracts", () -> new ByteArrayInputStream(contractBytes), "Contract.pdf", "application/pdf");

HttpRequest planChangeRequest = HttpRequest.newBuilder()
    .timeout(Duration.ofMinutes(1))
    .uri(Settings.wmsUri("user-portfolio-orders/modification"))
    .header("Accept-Language", "de-DE")
    .header("Authorization", "Bearer " + accessToken)
    .header("Content-Type", "multipart/form-data; boundary=" + multipartBody.getBoundary())
    .POST(multipartBody.build())
    .build();

response = httpClient.send(planChangeRequest, BodyHandlers.ofString());
Api.requireSuccess(response);
PortfolioOrderOutputModel modificationOrder = new Gson().fromJson(response.body(), PortfolioOrderOutputModel.class);
var modificationOrderId = modificationOrder.Id;

After a successful response, keep modificationOrderId and read its status using the order-status procedure. Refresh the portfolio to observe HasPendingOrder during processing.
When "HasPendingOrder" is TRUE, no plan modification is allowed.
Refresh the portfolio and order status after processing. Continue only when HasPendingOrder is false and the current lifecycle permits another modification; the API still validates eligibility.

Recognize the result

Illustrative modification-order response projection. Keep this new order ID; do not treat HTTP success as completion of the investment change.

{"id":457,"portfolioId":789,"orderStatusId":1}

The DTO catalog contains the remaining response properties. A failed request must be handled before advancing to the next step; see Error handling.