Added support for setting signing order for Create Participant Action. Exposed Template Id, Workspace Id & Data Fields for contract models. Product quantity changed from int to decimal to support new functionality of fractured quantity. (#3687)

This commit is contained in:
DmitryKleshchov 2024-11-07 17:49:53 +01:00 коммит произвёл GitHub
Родитель b99a5320e1
Коммит efac621568
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
2 изменённых файлов: 293 добавлений и 129 удалений

Просмотреть файл

@ -195,6 +195,14 @@
],
"default": "Company"
},
{
"name": "signing_order",
"in": "header",
"type": "number",
"description": "Sign order of participant",
"x-ms-visibility": "advanced",
"x-ms-summary": "Signing order"
},
{
"name": "party",
"in": "body",
@ -524,7 +532,48 @@
}
}
},
"/contracts/{contract_id}/": {
"/contracts/{contract_id}/": {
"get": {
"summary": "Get contract details",
"description": "Retrieve the details of a contract by its ID.",
"operationId": "GetContract",
"parameters": [
{
"name": "contract_id",
"in": "path",
"type": "string",
"required": true,
"x-ms-summary": "Contract Id",
"description": "Contract Id"
}
],
"responses": {
"200": {
"description": "Success",
"schema": {
"$ref": "#/definitions/Contract"
}
},
"400": {
"description": "Bad Request",
"schema": {
"type": "object"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"type": "object"
}
},
"404": {
"description": "Not Found",
"schema": {
"type": "object"
}
}
}
},
"delete": {
"summary": "Delete a contract",
"description": "Delete a specific contract by its ID.",
@ -576,51 +625,10 @@
"401": {
"description": "Unauthorized",
"schema": {
"type": "object"
"type": "object"
}
}
}
},
"get": {
"summary": "Get contract details",
"description": "Retrieve the details of a contract by its ID.",
"operationId": "GetContract",
"parameters": [
{
"name": "contract_id",
"in": "path",
"type": "string",
"required": true,
"x-ms-summary": "Contract Id",
"description": "Contract Id"
}
],
"responses": {
"200": {
"description": "Success",
"schema": {
"$ref": "#/definitions/Contract"
}
},
"400": {
"description": "Bad Request",
"schema": {
"type": "object"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"type": "object"
}
},
"404": {
"description": "Not Found",
"schema": {
"type": "object"
}
}
}
}
},
"/contracts/{contract_id}": {
@ -1628,9 +1636,7 @@
"workspace_id": {
"type": "integer",
"format": "int32",
"description": "workspace_id",
"x-ms-summary": "Workspace Id",
"x-ms-visibility": "internal"
"x-ms-summary": "Workspace Id"
}
},
"description": "Contract _private attributes"
@ -1645,9 +1651,7 @@
"template_id": {
"type": "integer",
"format": "int32",
"description": "template_id",
"x-ms-summary": "Template Id",
"x-ms-visibility": "internal"
"x-ms-summary": "Template Id"
},
"template_type_id": {
"type": "integer",
@ -1761,61 +1765,62 @@
"description": "Contract available_options attributes"
},
"data_fields": {
"x-ms-visibility": "internal",
"x-ms-summary": "Data Fields",
"x-ms-summary": "Data field",
"type": "array",
"items": {
"type": "object",
"properties": {
"_private_ownerside": {
"x-ms-summary": "Private Ownerside (Data Fields)",
"x-ms-summary": "private ownerside",
"type": "object",
"properties": {
"created_time": {
"type": "string",
"description": "created_time",
"x-ms-summary": "Created Time (Data Field)"
"description": "Data field created_time",
"x-ms-summary": "created time"
},
"custom_id": {
"type": "string",
"description": "custom_id",
"x-ms-summary": "Custom Id (Data Field)"
"description": "Data field custom_id",
"x-ms-summary": "custom id"
},
"updated_time": {
"type": "string",
"description": "updated_time",
"x-ms-summary": "Updated Time (Data Field)"
"description": "Data field updated_time",
"x-ms-summary": "Updated time"
}
},
"description": "_private_ownerside"
},
"description": {
"type": "string",
"description": "description",
"x-ms-summary": "Data Field Description"
"description": "Data field description",
"x-ms-summary": "description"
},
"id": {
"type": "integer",
"format": "int32",
"description": "id",
"x-ms-summary": "Data Field Id"
"description": "Data field id",
"x-ms-summary": "id"
},
"name": {
"type": "string",
"description": "name",
"x-ms-summary": "Data Field Name"
"description": "Data field name",
"x-ms-summary": "name"
},
"placeholder": {
"type": "string",
"description": "placeholder"
"description": "Data field placeholder",
"x-ms-summary": "placeholder"
},
"value": {
"type": "string",
"description": "value"
"description": "Data field value",
"x-ms-summary": "value"
}
}
},
"description": "data_fields"
"description": "Data fields collection"
},
"id": {
"type": "integer",
@ -2416,8 +2421,7 @@
"type": "object",
"properties": {
"amount": {
"type": "integer",
"format": "int32",
"type": "number",
"x-ms-summary": "amount"
},
"type": {
@ -2524,8 +2528,7 @@
"type": "object",
"properties": {
"amount": {
"type": "integer",
"format": "int32",
"type": "number",
"description": "Any positive number or 1/0 if the type is multiple_choice/single_choice",
"x-ms-summary": "Amount"
},

Просмотреть файл

@ -1,6 +1,6 @@
public class Script : ScriptBase
{
#region fields & consctructors
#region fields & constructors
private readonly Dictionary<string, Func<IScriptContext, Task<HttpResponseMessage>>> _operationMappings;
private readonly Dictionary<string, string> _schemaMappings;
public Script()
@ -180,7 +180,7 @@
try
{
var req = ctx.Request;
string contractId = GetHeaderStringValue(req.Headers, "contract_id");
string contractId = GetHeaderStringValue(req.Headers, Constants.CreatePartyContractIdHeader);
debugInfo.AppendLine($"contractId - {contractId}");
var inputPartyJson = await req.Content.ReadAsStringAsync();
@ -188,71 +188,160 @@
var inputParty = JsonConvert.DeserializeObject<Models.Party>(inputPartyJson);
var selectedType = GetHeaderStringValue(req.Headers, Constants.ParticipantTypeHeader);
debugInfo.AppendLine($"selectedType - {selectedType}");
int participantId = 0;
HttpResponseMessage result = null;
// for individual parties, just straight up create an individual participant.
if (selectedType.Equals(Constants.PartyTypes.Individual, StringComparison.OrdinalIgnoreCase))
{
debugInfo.AppendLine("identified as individual. creating individual party.");
return await CreateParty(req, contractId, inputParty, ctx);
}
// call getParties
debugInfo.AppendLine("trying to get existing parties.");
var getPartiesRequest = new HttpRequestMessage(
HttpMethod.Get,
new Uri(string.Format(Constants.Requests.PartyEndpoint, req.RequestUri.Host, contractId)));
CopyHeaders(req, getPartiesRequest);
var getPartiesResponse = await ctx.SendAsync(getPartiesRequest, CancellationToken)
.ConfigureAwait(false);
if (!getPartiesResponse.IsSuccessStatusCode)
{
return getPartiesResponse;
}
debugInfo.AppendLine("converting get party response.");
var getPartiesResponseJson = await getPartiesResponse.Content.ReadAsStringAsync();
var existingParties = JsonConvert.DeserializeObject<Responses.GetPartiesResponse>(getPartiesResponseJson).data;
int matchingPartyId = 0;
// if we need to create an ownserside participant, just find my_party and create a participant for it.
if (selectedType.Equals(Constants.PartyTypes.Ownerside, StringComparison.OrdinalIgnoreCase))
{
matchingPartyId = existingParties.FirstOrDefault(x =>
x.my_party.Value).id ??
throw new ScriptException(HttpStatusCode.InternalServerError,
$"Couldn't find ownerside party for contract {contractId}");
return await CreateParticipant(req, matchingPartyId, contractId, inputParty.participant, ctx);
}
// process Company participants.
debugInfo.AppendLine($"Input party - {Environment.NewLine + inputParty}");
foreach (Models.Party existingParty in existingParties)
{
debugInfo.AppendLine($"Trying to match with party - {Environment.NewLine + existingParty}");
if (existingParty.Equals(inputParty))
result = await CreateParty(req, contractId, inputParty, ctx);
if (!result.IsSuccessStatusCode)
{
debugInfo.AppendLine("match");
matchingPartyId = existingParty.id.Value;
break;
return result;
}
debugInfo.AppendLine("not match");
string responseStr = await result.Content.ReadAsStringAsync();
JObject partyResponse = JObject.Parse(responseStr);
participantId = partyResponse["participant"]["id"].ToObject<int>();
}
debugInfo.Append($"matching party id: {Environment.NewLine + matchingPartyId}");
if (matchingPartyId != default)
else
{
debugInfo.AppendLine("Party found. creating participant.");
return await CreateParticipant(req, matchingPartyId, contractId, inputParty.participant, ctx);
// call getParties
debugInfo.AppendLine("trying to get existing parties.");
var getPartiesRequest = new HttpRequestMessage(
HttpMethod.Get,
new Uri(string.Format(Constants.Requests.PartyEndpoint, req.RequestUri.Host, contractId)));
CopyHeaders(req, getPartiesRequest);
var getPartiesResponse = await ctx.SendAsync(getPartiesRequest, CancellationToken)
.ConfigureAwait(false);
if (!getPartiesResponse.IsSuccessStatusCode)
{
return getPartiesResponse;
}
debugInfo.AppendLine("converting get party response.");
var getPartiesResponseJson = await getPartiesResponse.Content.ReadAsStringAsync();
var existingParties = JsonConvert.DeserializeObject<Responses.GetPartiesResponse>(getPartiesResponseJson).data;
int matchingPartyId = 0;
// if we need to create an ownserside participant, just find my_party and create a participant for it.
if (selectedType.Equals(Constants.PartyTypes.Ownerside, StringComparison.OrdinalIgnoreCase))
{
matchingPartyId = existingParties.FirstOrDefault(x =>
x.my_party.Value).id ??
throw new ScriptException(HttpStatusCode.InternalServerError,
$"Couldn't find ownerside party for contract {contractId}");
result = await CreateParticipant(req, matchingPartyId, contractId, inputParty.participant, ctx);
if (!result.IsSuccessStatusCode)
{
return result;
}
string responseStr = await result.Content.ReadAsStringAsync();
JObject partyResponse = JObject.Parse(responseStr);
participantId = partyResponse["id"].ToObject<int>();
}
else
{
// process Company participants.
debugInfo.AppendLine($"Input party - {Environment.NewLine + inputParty}");
foreach (Models.Party existingParty in existingParties)
{
debugInfo.AppendLine($"Trying to match with party - {Environment.NewLine + existingParty}");
if (existingParty.Equals(inputParty))
{
debugInfo.AppendLine("match");
matchingPartyId = existingParty.id.Value;
break;
}
debugInfo.AppendLine("not match");
}
debugInfo.Append($"matching party id: {Environment.NewLine + matchingPartyId}");
if (matchingPartyId != default)
{
debugInfo.AppendLine("Party found. creating participant.");
result = await CreateParticipant(req, matchingPartyId, contractId, inputParty.participant, ctx);
if (!result.IsSuccessStatusCode)
{
return result;
}
string responseStr = await result.Content.ReadAsStringAsync();
JObject partyResponse = JObject.Parse(responseStr);
participantId = partyResponse["id"].ToObject<int>();
}
else
{
debugInfo.AppendLine("Party not found. creating new party.");
result = await CreateParty(req, contractId, inputParty, ctx);
if (!result.IsSuccessStatusCode)
{
return result;
}
string responseStr = await result.Content.ReadAsStringAsync();
JObject partyResponse = JObject.Parse(responseStr);
participantId = partyResponse["participants"].ElementAt(0)["id"].ToObject<int>();
}
}
}
string signOrder = GetHeaderStringValue(req.Headers, Constants.SigningOrderHeader);
if (!string.IsNullOrEmpty(signOrder) && Int32.TryParse(signOrder, out int orderInt) && participantId != default)
{
debugInfo.AppendLine($"setting sign order.");
var getContractRequest = new HttpRequestMessage(
HttpMethod.Get,
new Uri(string.Format(Constants.Requests.ContractsEndpoint, req.RequestUri.Host, contractId)));
CopyHeaders(req, getContractRequest);
var getContractResponse = await ctx.SendAsync(getContractRequest, CancellationToken)
.ConfigureAwait(false);
if (!getContractResponse.IsSuccessStatusCode) return getContractResponse;
debugInfo.AppendLine($"retrieved contract");
string responseStr = await getContractResponse.Content.ReadAsStringAsync();
var contract = JsonConvert.DeserializeObject<Models.Contract>(responseStr);
var participants = new List<Models.Participant>();
foreach (Models.Party party in contract.parties)
{
if (party.participants != null && party.participants.Any())
{
participants.AddRange(party.participants);
}
else if (party.participant != null)
{
participants.Add(party.participant);
}
}
debugInfo.AppendLine($"extracted participants.");
if (contract.sign_order != null && contract.sign_order.Any())
{
foreach (var participant in participants)
{
var signorderItem = contract.sign_order.FirstOrDefault(x => x.participant_id == participant.id.Value);
if (signorderItem != null) participant.private_ownerside.signOrder = signorderItem.order;
}
debugInfo.AppendLine($"synced sign orders with participants.");
}
debugInfo.AppendLine($"$Participants: {String.Join($"{Environment.NewLine},", participants.Select(x => JsonConvert.SerializeObject(x)))}");
participants = participants.OrderBy(x => x.private_ownerside.created_time).ToList();
debugInfo.AppendLine($"sorted participants.");
var signOrderResponse = await SetParticipantSignOrder(req, debugInfo, participants, contractId, participantId, orderInt, ctx);
if (!signOrderResponse.IsSuccessStatusCode)
{
return signOrderResponse;
}
}
debugInfo.AppendLine("Party not found. creating new party.");
return await CreateParty(req, contractId, inputParty, ctx);
return result;
}
catch (Exception e)
{
@ -260,6 +349,37 @@
}
}
private async Task<HttpResponseMessage> SetParticipantSignOrder(HttpRequestMessage initialRequest, StringBuilder debugInfo, List<Models.Participant> existingParticipants, string contractId, int participantId, int order, IScriptContext ctx)
{
string requestUrl = string.Format(Constants.Requests.ContractsEndpoint, initialRequest.RequestUri.Host, contractId);
HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Put, new Uri(requestUrl));
CopyHeaders(initialRequest, req);
var signOrder = new List<Models.SignOrderItem>();
int counter = 1;
foreach (var participant in existingParticipants)
{
var signOrderItem = new Models.SignOrderItem() { participant_id = participant.id.Value };
debugInfo.AppendLine($"1");
if (participant.private_ownerside.signOrder != default)
{
signOrderItem.order = participant.private_ownerside.signOrder; debugInfo.AppendLine($"2");
}
else if (participant.id == participantId) { signOrderItem.order = order; debugInfo.AppendLine($"3"); }
else { signOrderItem.order = counter++; debugInfo.AppendLine($"4"); }
signOrder.Add(signOrderItem);
}
debugInfo.AppendLine($"assigned sign order to every participant.");
var bodyObj = new Models.Contract()
{
sign_order = signOrder
};
var jsonBody = JsonConvert.SerializeObject(bodyObj);
req.Content = new StringContent(jsonBody, Encoding.UTF8, "application/json");
return await ctx.SendAsync(req, CancellationToken);
}
private async Task<HttpResponseMessage> HandleGetTemplateTypeByTemplateId(IScriptContext ctx)
{
var req = ctx.Request;
@ -430,6 +550,7 @@
public const string ContractProductGroupsEndpoint = "https://{0}/v1/contracts/{1}/product_groups";
public const string AddProductEndpoint = "https://{0}/v1/contracts/{1}/product_groups/{2}/products";
public const string TemplatesEndpoint = "https://{0}/v1/templates/{1}";
public const string ContractsEndpoint = "https://{0}/v1/contracts/{1}";
}
public static class PartyTypes
{
@ -440,7 +561,9 @@
public const string GetTemplatesFilterHeader = "x-oneflow-workspace-id";
public const string TemplateIdHeader = "x-oneflow-template-id";
public const string SchemaIdHeader = "schema_id";
public const string CreatePartyContractIdHeader = "contract_id";
public const string ParticipantTypeHeader = "participant_type";
public const string SigningOrderHeader = "signing_order";
public const string UserEmailOverrideHeader = "x-oneflow-user-email-override";
public const string UserEmailHeader = "x-oneflow-user-email";
public const string ProductGroupIndexHeader = "x-ms-oneflow-product-group-index";
@ -716,6 +839,7 @@
}";
#endregion
}
public class Models
{
public class Party
@ -804,6 +928,7 @@
return sb.ToString();
}
public void AlignParticipants()
{
if (Constants.PartyTypes.Individual.Equals(type, StringComparison.OrdinalIgnoreCase)) return;
@ -815,8 +940,20 @@
}
}
public class ParticipantPrivateOwnerside
{
//[JsonConverter(typeof(DateFormatConverter), "o")] // 2023-04-27T11:29:36+00:00
public DateTime created_time { get; set; }
[JsonIgnore]
public int signOrder { get; set; }
}
public class Participant
{
[JsonProperty("_private_ownerside", NullValueHandling = NullValueHandling.Ignore)]
public ParticipantPrivateOwnerside private_ownerside { get; set; }
//workaround for Power Automate bug with a nested object in array element.
[JsonProperty("_permissions/contract:update", NullValueHandling = NullValueHandling.Ignore)]
bool? updatePermission { get; set; }
@ -945,6 +1082,22 @@
public TemplateType template_type { get; set; }
}
public class Contract
{
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public IEnumerable<Party> parties { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public IEnumerable<SignOrderItem> sign_order { get; set; }
}
public class SignOrderItem
{
public int participant_id { get; set; }
public int order { get; set; }
}
public class TemplateType
{
public string extension_type { get; set; }
@ -998,5 +1151,13 @@
}
}
public class DateFormatConverter : Newtonsoft.Json.Converters.IsoDateTimeConverter
{
public DateFormatConverter(string format)
{
DateTimeFormat = format;
}
}
#endregion
}
}