Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- using Microsoft.Crm.Sdk.Messages;
- using Microsoft.Xrm.Sdk;
- using Microsoft.Xrm.Sdk.Client;
- using Microsoft.Xrm.Sdk.Messages;
- using Microsoft.Xrm.Sdk.Query;
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Linq;
- using System.ServiceModel.Description;
- using System.Web.Script.Serialization;
- namespace SyncWriter.Common
- {
- public static class Utils
- {
- const string EntidadNoProc = "new_registroprocesado";
- public static int CantidadErroresSincronizacion{ get; set; }
- private static IOrganizationService _crmService;
- public static void Init(IOrganizationService service)
- {
- _crmService = service;
- }
- public static void ManageTransformErrorsRetry(IEnumerable<object> source, EntityModel Model, Guid syncId, IDictionary<object, object> RegNoProcesados)
- {
- var ListEntities = new List<Entity>();
- // An error has occurred.
- foreach (var record in source)
- {
- string idRecord = "";
- //var RecordValue = record.GetType().GetProperty(Model.Fields.First(x => x.IsSourceId.Equals(true)).SourceFieldName).GetValue(record);
- //idRecord = RegNoProcesados.First(y => y.Key.ToString() == RecordValue.ToString()).Value.ToString();
- idRecord = Model.GetCRMGuidKey(RegNoProcesados, record);
- var EntidadError = new Entity(EntidadNoProc);
- EntidadError.Attributes.Add("new_registroprocesadoid", new Guid(idRecord));
- EntidadError.Attributes.Add("statuscode", new OptionSetValue(100000003));
- EntidadError.Attributes.Add("new_corregido", false);
- ListEntities.Add(EntidadError);
- }
- if (ListEntities.Count > 0)
- {
- ExecuteErrorMultipleRequest(ListEntities, false);
- }
- }
- public static void ManageTransformErrors(string Mensaje, List<Tuple<object, string>> source, EntityModel model, Guid syncId)
- {
- var ListEntities = new List<Entity>();
- // An error has occurred.
- foreach (var record in source)
- {
- var sourceData = new JavaScriptSerializer().Serialize(record);
- var EntidadError = new Entity(EntidadNoProc);
- EntidadError.Attributes.Add("new_fechadeinicio", DateTime.Now);
- EntidadError.Attributes.Add("new_descripcioncodigo", model.EntityName);
- EntidadError.Attributes.Add("new_xmlorigen", sourceData);
- EntidadError.Attributes.Add("new_corregido", false);
- var recordKeyName = model.Fields.First(x => x.IsSourceId).SourceFieldName;
- var recordKey = record.Item1.GetType().GetProperty(recordKeyName).GetValue(record.Item1);
- EntidadError.Attributes.Add("new_registroid", recordKey.ToString());
- EntidadError.Attributes.Add("new_syncid", new EntityReference("new_sincronizacion", syncId));
- EntidadError.Attributes.Add("statuscode", new OptionSetValue(100000000));
- EntidadError.Attributes.Add("new_fechadefin", DateTime.Now);
- var MensajeCampo = Mensaje.Replace("{{NOMBRECAMPO}}", model.Fields.First(x => x.SourceFieldName == record.Item2).EntityFieldName);
- var valorDatoString = "";
- if (record.Item2.Contains("."))
- {
- var ValorDato = record.Item1.GetType().GetProperty(record.Item2.Substring(0, record.Item2.IndexOf("."))).GetValue(record.Item1).GetType().GetProperty(record.Item2.Substring(record.Item2.IndexOf(".") + 1)).GetValue(record.Item1.GetType().GetProperty(record.Item2.Substring(0, record.Item2.IndexOf("."))).GetValue(record.Item1));
- if (ValorDato != null)
- {
- valorDatoString = ValorDato.ToString();
- if (!string.IsNullOrWhiteSpace(valorDatoString))
- {
- valorDatoString = "\"" + valorDatoString + "\"";
- }
- else
- {
- valorDatoString = "\"\"";
- }
- }
- else
- {
- valorDatoString = "\"null\"";
- }
- }
- else
- {
- var ValorDato = record.Item1.GetType().GetProperty(record.Item2).GetValue(record.Item1);
- if (ValorDato != null)
- {
- valorDatoString = ValorDato.ToString();
- if (!string.IsNullOrWhiteSpace(valorDatoString))
- {
- valorDatoString = "\"" + valorDatoString + "\"";
- }
- else
- {
- valorDatoString = "\"\"";
- }
- }
- else
- {
- valorDatoString = "null";
- }
- }
- var MensajeCompleto = MensajeCampo.Replace("{{VALOR}}", valorDatoString);
- EntidadError.Attributes.Add("new_errordescripcion", MensajeCompleto);
- ListEntities.Add(EntidadError);
- }
- if (ListEntities.Count > 0)
- {
- ExecuteErrorMultipleRequest(ListEntities, true);
- }
- }
- public static void ErrorServicio(Guid IdSync, Exception ex)
- {
- var sync = new Entity("new_sincronizacion");
- sync["new_sincronizacionid"] = IdSync;
- sync["new_error"] = "Mensaje: " + ex.Message + (ex.InnerException != null ? " InnerException: " + ex.InnerException.Message : "") + ex.StackTrace;
- sync["statecode"] = new OptionSetValue(1);
- sync["statuscode"] = new OptionSetValue(2);
- _crmService.Update(sync);
- }
- public static void ExecuteErrorMultipleRequest(List<Entity> ListEntities, bool Crear)
- {
- ExecuteMultipleRequest requestWithResults = null;
- requestWithResults = new ExecuteMultipleRequest()
- {
- // Assign settings that define execution behavior: continue on error, return responses.
- Settings = new ExecuteMultipleSettings()
- {
- ContinueOnError = true,
- ReturnResponses = false
- },
- // Create an empty organization request collection.
- Requests = new OrganizationRequestCollection()
- };
- // Create several (local, in memory) entities in a collection.
- EntityCollection input = new EntityCollection(ListEntities) { EntityName = EntidadNoProc };
- // Add a CreateRequest for each entity to the request collection.
- foreach (var entity in input.Entities)
- {
- OrganizationRequest Request;
- if (Crear)
- {
- Request = new CreateRequest { Target = entity };
- requestWithResults.Requests.Add(Request);
- }
- else
- {
- Request = new UpdateRequest { Target = entity };
- requestWithResults.Requests.Add(Request);
- }
- }
- // Execute all the requests in the request collection using a single web method call.
- ExecuteMultipleResponse responseWithResults =
- (ExecuteMultipleResponse)_crmService.Execute(requestWithResults);
- }
- public static int ManageErrors(ExecuteMultipleResponse results, ExecuteMultipleRequest transformedData, Guid syncId, EntityModel Model)
- {
- if (results.Responses.Any(x => x.Fault != null))
- {
- var ListEntities = new List<Entity>();
- // Display the results returned in the responses.
- foreach (var responseItem in results.Responses.Where(x => x.Fault != null))
- {
- string sourceData = "";
- // An error has occurred.
- if (transformedData.Requests.ElementAt(responseItem.RequestIndex).Parameters.Values.Cast<Entity>().First().GetType() == typeof(Entity))
- {
- sourceData = new JavaScriptSerializer().Serialize(ToCRMModel(Model, transformedData.Requests.ElementAt(responseItem.RequestIndex).Parameters.Values.Cast<Entity>().First().Attributes));
- }
- else
- {
- //sourceData = new JavaScriptSerializer().Serialize(ToCRMModel(Model, transformedData.Requests.ElementAt(responseItem.RequestIndex).Parameters.Values.Cast<Entity>().First().Attributes));
- }
- var EntidadError = new Entity(EntidadNoProc);
- EntidadError.Attributes.Add("new_fechadeinicio", DateTime.Now);
- EntidadError.Attributes.Add("new_descripcioncodigo", Model.EntityName);
- //EntidadError = new JavaScriptSerializer().Serialize(ToSourceObject(Model,source));
- EntidadError.Attributes.Add("new_xmltransformado", sourceData);
- string MensajeError = responseItem.Fault.Message;
- if (MensajeError.Contains("00000000-0000-0000-0000-000000000000"))
- {
- //MensajeError = MensajeError.Replace("00000000-0000-0000-0000-000000000000",
- //transformedData.Requests.ElementAt(responseItem.RequestIndex)
- //.Parameters.Values.Cast<Entity>().First().Attributes
- //.First(x => x.Key.ToString() == Model.Fields
- //.First(y => y.IsSourceId.Equals(true)).EntityFieldName.ToString())
- //.Value.ToString());
- var entity = transformedData.Requests.ElementAt(responseItem.RequestIndex)
- .Parameters.Values.Cast<Entity>().FirstOrDefault();
- MensajeError = MensajeError.Replace("00000000-0000-0000-0000-000000000000", Model.getCompositeKeyValueEntity(entity).ToString());
- }
- EntidadError.Attributes.Add("new_errordescripcion", MensajeError);
- if (responseItem.Fault.ErrorDetails.Any(x => x.Key == "CallStack"))
- {
- EntidadError.Attributes.Add("new_errorstack", responseItem.Fault.ErrorDetails.First(x => x.Key == "CallStack").Value);
- }
- else
- {
- EntidadError.Attributes.Add("new_errorstack", responseItem.Fault.TraceText);
- }
- EntidadError.Attributes.Add("new_corregido", false);
- EntidadError.Attributes.Add("new_registroid", transformedData.Requests.ElementAt(responseItem.RequestIndex).Parameters.Values.Cast<Entity>().First().Attributes.First(x => x.Key.ToString() == Model.Fields.First(y => y.IsSourceId.Equals(true)).EntityFieldName.ToString()).Value.ToString());
- EntidadError.Attributes.Add("new_syncid", new EntityReference("new_sincronizacion", syncId));
- if (transformedData.RequestName == "Create")
- {
- EntidadError.Attributes.Add("new_operacion", new OptionSetValue(100000000));
- }
- else
- {
- EntidadError.Attributes.Add("new_operacion", new OptionSetValue(100000001));
- }
- EntidadError.Attributes.Add("statuscode", new OptionSetValue(100000001));
- EntidadError.Attributes.Add("new_fechadefin", DateTime.Now);
- ListEntities.Add(EntidadError);
- }
- ExecuteErrorMultipleRequest(ListEntities, true);
- }
- return results.Responses.Where(x => x.Fault != null).Count();
- }
- public static int PostUpdate(IDictionary<Entity, int> records, Guid syncId, EntityModel model)
- {
- ExecuteMultipleRequest requestWithResults = null;
- requestWithResults = new ExecuteMultipleRequest()
- {
- // Assign settings that define execution behavior: continue on error, return responses.
- Settings = new ExecuteMultipleSettings()
- {
- ContinueOnError = true,
- ReturnResponses = false
- },
- // Create an empty organization request collection.
- Requests = new OrganizationRequestCollection()
- };
- var query = model.getEntitiesByKeyQuery(records.Select(x => x.Key).ToArray());
- var entities = _crmService.RetrieveMultiple(query);
- foreach (var entity in entities.Entities)
- {
- SetStateRequest stateRequest = new SetStateRequest();
- // Set the Request Object's Properties
- stateRequest.State = new OptionSetValue(1);
- var KeyEntity = model.getCompositeKeyValueEntity(entity);
- stateRequest.Status =
- new OptionSetValue(records
- .FirstOrDefault(x => model.getCompositeKeyValueEntity(x.Key).ToString()
- == KeyEntity.ToString()).Value);
- // Point the Request to the case whose state is being changed
- stateRequest.EntityMoniker = entity.ToEntityReference();
- requestWithResults.Requests.Add(stateRequest);
- }
- ExecuteMultipleResponse response = (ExecuteMultipleResponse)_crmService.Execute(requestWithResults);
- return ManageErrors(response, requestWithResults, syncId, model);
- }
- public static int ManageErrors(ExecuteMultipleResponse results, ExecuteMultipleRequest transformedData, Guid syncId, string AttrToFind, EntityModel Model)
- {
- if (results.Responses.Any(x => x.Fault != null))
- {
- var ListEntities = new List<Entity>();
- // Display the results returned in the responses.
- foreach (var responseItem in results.Responses.Where(x => x.Fault != null))
- {
- string sourceData = "";
- // An error has occurred.
- if (transformedData.Requests.ElementAt(responseItem.RequestIndex).Parameters.Values.Cast<Entity>().First().GetType() == typeof(Entity))
- {
- sourceData = new JavaScriptSerializer().Serialize(ToCRMModel(Model, transformedData.Requests.ElementAt(responseItem.RequestIndex).Parameters.Values.Cast<Entity>().First().Attributes));
- }
- else
- {
- //sourceData = new JavaScriptSerializer().Serialize(ToCRMModel(Model, transformedData.Requests.ElementAt(responseItem.RequestIndex).Parameters.Values.Cast<EntityReference>().First().Id));
- }
- var EntidadError = new Entity(EntidadNoProc);
- EntidadError.Attributes.Add("new_fechadeinicio", DateTime.Now);
- EntidadError.Attributes.Add("new_descripcioncodigo", Model.EntityName);
- EntidadError.Attributes.Add("new_xmltransformado", sourceData);
- string MensajeError = responseItem.Fault.Message;
- if (MensajeError.Contains("00000000-0000-0000-0000-000000000000"))
- {
- MensajeError = MensajeError.Replace("00000000-0000-0000-0000-000000000000", transformedData.Requests.ElementAt(responseItem.RequestIndex).Parameters.Values.Cast<Entity>().First().Attributes.First(x => x.Key.ToString() == Model.Fields.First(y => y.IsSourceId.Equals(true)).EntityFieldName.ToString()).Value.ToString());
- }
- EntidadError.Attributes.Add("new_errordescripcion", MensajeError);
- if (responseItem.Fault.ErrorDetails.Any(x => x.Key == "CallStack"))
- {
- EntidadError.Attributes.Add("new_errorstack", responseItem.Fault.ErrorDetails.First(x => x.Key == "CallStack").Value);
- }
- else
- {
- EntidadError.Attributes.Add("new_errorstack", responseItem.Fault.TraceText);
- }
- EntidadError.Attributes.Add("new_corregido", false);
- var RegistroId = "";
- if (transformedData.Requests.ElementAt(responseItem.RequestIndex).GetType() == typeof(Entity))
- {
- RegistroId = transformedData.Requests.ElementAt(responseItem.RequestIndex).Parameters.Values.Cast<Entity>().First().Attributes.First(x => x.Key.ToString() == Model.Fields.First(y => y.IsSourceId.Equals(true)).EntityFieldName.ToString()).Value.ToString();
- }
- EntidadError.Attributes.Add("new_registroid", RegistroId);
- EntidadError.Attributes.Add("new_syncid", new EntityReference("new_sincronizacion", syncId));
- if (transformedData.RequestName == "Create")
- {
- EntidadError.Attributes.Add("new_operacion", new OptionSetValue(100000000));
- }
- else
- {
- EntidadError.Attributes.Add("new_operacion", new OptionSetValue(100000001));
- }
- EntidadError.Attributes.Add("statuscode", new OptionSetValue(100000001));
- EntidadError.Attributes.Add("new_fechadefin", DateTime.Now);
- ListEntities.Add(EntidadError);
- }
- ExecuteErrorMultipleRequest(ListEntities, true);
- }
- return results.Responses.Where(x => x.Fault != null).Count();
- }
- public static int ManageErrors(ExecuteMultipleResponse results, ExecuteMultipleRequest source, IDictionary<object, object> RegNoProcesados, EntityModel Model)
- {
- var ListEntities = new List<Entity>();
- // Display the results returned in the responses.
- foreach (var responseItem in results.Responses)
- {
- string idRecord = "";
- idRecord = RegNoProcesados.First(x => x.Key.ToString() == source.Requests.ElementAt(responseItem.RequestIndex).Parameters.Values.Cast<Entity>().First().Attributes.First(y => y.Key.ToString() == Model.Fields.First(z => z.IsSourceId.Equals(true)).EntityFieldName.ToString()).Value.ToString()).Value.ToString();
- var EntidadError = new Entity(EntidadNoProc);
- EntidadError.Attributes.Add("new_registroprocesadoid", new Guid(idRecord));
- if (responseItem.Fault == null)
- {
- EntidadError.Attributes.Add("statuscode", new OptionSetValue(1));
- EntidadError.Attributes.Add("new_corregido", true);
- }
- else
- {
- EntidadError.Attributes.Add("statuscode", new OptionSetValue(100000003));
- EntidadError.Attributes.Add("new_corregido", false);
- }
- ListEntities.Add(EntidadError);
- }
- ExecuteErrorMultipleRequest(ListEntities, false);
- return results.Responses.Where(x => x.Fault != null).Count();
- }
- public static IOrganizationService GetCRMService()
- {
- return _crmService;
- }
- public static IOrganizationService GetCRMServiceInstance()
- {
- Uri oUri = new Uri("https://trpdemotest.crm2.dynamics.com" + "/XRMServices/2011/Organization.svc");
- ClientCredentials clientCredentials = new ClientCredentials();
- clientCredentials.UserName.UserName = "jgoldman@trpdemotest.onmicrosoft.com";
- clientCredentials.UserName.Password = "Pass@word1";
- var crmInstance = new OrganizationServiceProxy(oUri, null, clientCredentials, null);
- crmInstance.EnableProxyTypes();
- return crmInstance;
- }
- public static Entity getEntityWithId(Entity entity, string AttrToFind)
- {
- var query = new QueryExpression(entity.LogicalName);
- query.Criteria.AddCondition(new ConditionExpression(AttrToFind, ConditionOperator.Equal, entity.Attributes[AttrToFind]));
- var response = _crmService.RetrieveMultiple(query);
- if (response.Entities.Count > 0)
- {
- return entity;
- }
- else
- {
- return null;
- }
- }
- public static IDictionary<object, object> getIdsToBlock(IEnumerable<Tuple<string, string[]>> data, EntityModel model)
- {
- var query = model.getIdsQuery(data);
- var response = _crmService.RetrieveMultiple(query);
- return response.Entities.ToDictionary(x => model.getCompositeKeyValueEntity(x), x => x.Attributes[model.EntityId]);
- }
- public static Tuple<string, IDictionary<object, object>> GetReferenceData(string[] data, Dependency dependency)
- {
- var query = new QueryExpression(dependency.EntityToJoin);
- query.ColumnSet.AddColumn(dependency.EntityFieldToJoin);
- query.Criteria.AddCondition(new ConditionExpression(dependency.EntityFieldToJoin, ConditionOperator.In, data));
- var response = _crmService.RetrieveMultiple(query);
- var dic = new Dictionary<object, object>();
- foreach (var d in data.Distinct())
- {
- var value = response.Entities.FirstOrDefault(x => x.Attributes[dependency.EntityFieldToJoin].Equals(d)) == null ? "-1" : response.Entities.First(x => x.Attributes[dependency.EntityFieldToJoin].Equals(d)).Attributes[dependency.EntityToJoinId];
- dic.Add(d, value);
- }
- return new Tuple<string, IDictionary<object, object>>(dependency.EntityToJoin,
- dic);
- //return new Tuple<string, IDictionary<object, object>>(dependency.EntityToJoin,
- // response.Entities.ToDictionary(x => x.Attributes[dependency.EntityFieldToJoin],
- // x => x.Attributes[dependency.EntityToJoinId] == null ? -1 :
- // x.Attributes[dependency.EntityToJoinId]));
- }
- public static EntityModel GetModel<T>() where T : class
- {
- var type = typeof(T);
- var name = type.Name.Replace("SyncPlugin", "");
- var path = "SyncWriter.Mapping." + name + "Map.json";
- EntityModel model;
- using (Stream fileStream = type.Assembly.GetManifestResourceStream(path))
- {
- var sr = new StreamReader(fileStream);
- model = new JavaScriptSerializer().Deserialize<EntityModel>(sr.ReadToEnd());
- }
- var list = model.Fields
- .Select(x => new { FieldName = x.SourceFieldName }).ToList<object>();
- model.EntityType = EntityTypeBuilder.CompileResultType(list);
- var crmList = model.Fields
- .Select(x => new { FieldName = x.EntityFieldName }).ToList<object>();
- if (model.Dependencies.Any())
- crmList.AddRange(model.Dependencies.Select(x => new { FieldName = x.EntityToJoinId }));
- crmList.Add(new { FieldName = model.EntityId });
- model.CRMModel = EntityTypeBuilder.CompileResultType(crmList);
- return model;
- }
- public static object ToSourceObject(EntityModel model, IDictionary<string, object> attrs)
- {
- var obj = Activator.CreateInstance(model.EntityType);
- foreach (var field in model.Fields)
- {
- var value = attrs.First(x => x.Key == field.EntityFieldName).Value;
- obj.GetType().GetProperty(field.SourceFieldName).SetValue(obj, value);
- }
- return obj;
- }
- public static object ToCRMModel(EntityModel model, AttributeCollection attrs)
- {
- var obj = Activator.CreateInstance(model.CRMModel);
- foreach (var field in model.Fields)
- {
- var value = GetCRMValue(attrs.First(x => x.Key == field.EntityFieldName));
- obj.GetType().GetProperty(field.EntityFieldName).SetValue(obj, value);
- }
- foreach (var dependency in model.Dependencies)
- {
- var value = GetCRMValue(attrs.First(x => x.Key == dependency.EntityLookupName));
- obj.GetType().GetProperty(dependency.EntityToJoinId).SetValue(obj, value);
- }
- if (attrs.Any(x => x.Key == model.EntityId))
- {
- obj.GetType().GetProperty(model.EntityId).SetValue(obj, GetCRMValue(attrs.First(x => x.Key == model.EntityId)));
- }
- return obj;
- }
- private static object GetCRMValue(KeyValuePair<string, object> attr)
- {
- object CrmValue = null;
- if (attr.Value != null)
- {
- if (attr.Value.GetType() == typeof(EntityReference))
- {
- CrmValue = ((EntityReference)attr.Value).Id.ToString();
- }
- else if (attr.Value.GetType() == typeof(OptionSetValue))
- {
- CrmValue = ((OptionSetValue)attr.Value).Value.ToString();
- }
- else
- {
- CrmValue = attr.Value.ToString();
- }
- }
- return CrmValue;
- }
- public static object ToSourceObject(EntityModel model, AttributeCollection attrs)
- {
- var obj = Activator.CreateInstance(model.EntityType);
- foreach (var field in model.Fields)
- {
- var value = attrs.First(x => x.Key == field.EntityFieldName).Value;
- obj.GetType().GetProperty(field.SourceFieldName).SetValue(obj, value);
- }
- return obj;
- }
- public static object ToCRMType(this object ObjectSource, Field ModelField)
- {
- object ObjectReturn = ObjectSource;
- if (ObjectSource != null)
- {
- if (!string.IsNullOrWhiteSpace(ObjectSource.ToString()))
- {
- try
- {
- switch (ModelField.CRMType)
- {
- case CRMTypes.STRING:
- ObjectReturn = Convert.ToString(ObjectSource);
- break;
- case CRMTypes.INT:
- ObjectReturn = Convert.ToInt32(ObjectSource);
- break;
- case CRMTypes.DATETIME:
- ObjectReturn = Convert.ToDateTime(ObjectSource);
- break;
- case CRMTypes.DOUBLE:
- ObjectReturn = Convert.ToDouble(ObjectSource);
- break;
- case CRMTypes.BOOL:
- ObjectReturn = Convert.ToBoolean(ObjectSource);
- break;
- case CRMTypes.DECIMAL:
- ObjectReturn = Convert.ToDecimal(ObjectSource);
- break;
- case CRMTypes.MONEY:
- ObjectReturn = new Money(Convert.ToDecimal(ObjectSource));
- break;
- }
- }
- catch (Exception)
- {
- return null;
- }
- }
- else
- {
- if (ModelField.CRMType == CRMTypes.STRING)
- {
- ObjectReturn = Convert.ToString(ObjectSource);
- }
- else
- {
- return null;
- }
- }
- }
- return ObjectReturn;
- }
- public enum CRMTypes
- {
- STRING = 0,
- INT,
- DATETIME,
- BOOL,
- DOUBLE,
- DECIMAL,
- MONEY
- }
- }
- //public class EntityModel
- //{
- //}
- public class Field
- {
- public string SourceFieldName { get; set; }
- public string EntityFieldName { get; set; }
- public bool IsSourceId { get; set; }
- public bool ErrorOnNullOrEmpty { get; set; }
- public bool isOptionValue { get; set; }
- public Utils.CRMTypes CRMType { get; set; }
- public bool IsDependency { get; set; }
- }
- public class Dependency
- {
- public string EntityToJoin { get; set; }
- public string EntityToJoinId { get; set; }
- public string EntityFieldToJoin { get; set; }
- public string SourceFieldToJoin { get; set; }
- public string EntityLookupName { get; set; }
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement