Unable to reset Shopify API compare at price using ShopifySharp - shopify-api

I used to be able to set compare_at_price to null after a product was on sale but now I get an error. This is making use of the ShopifySharp C# library from NozzleGear.
Example:
Price was: 1000, Compare at 1200
I want to reset it the Price to 1200, with compare at 0.
(422 Unprocessable Entity) compare_at_price: Compare at price needs to be higher than Price
I can neither set it to 0, so how will I disable the compare_at_price to 0 or null or remove it?
var variant = await productVariantService.UpdateAsync(product.VariantId.Value, new ProductVariant()
{
Price = updatePrice,
CompareAtPrice = updateCompareAtPrice
});

There seems to be a bug, here is the workaround...
https://github.com/nozzlegear/ShopifySharp/issues/373
Create and use the below Service
using System;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using ShopifySharp;
using ShopifySharp.Infrastructure;
public class ProductVariantServiceEx : ProductVariantService
{
public ProductVariantServiceEx(string myShopifyUrl, string shopAccessToken) : base(myShopifyUrl, shopAccessToken) { }
public virtual async Task<ProductVariant> UpdateAsync(long productVariantId, ProductVariant variant)
{
try
{
// BUGFIX: If variant.CompareAtPrice is set to 0, then force it to empty
string json = new JsonContent(new { variant }).ReadAsStringAsync().Result;
if (json.Contains("\"compare_at_price\":0.00}")
|| json.Contains("\"compare_at_price\":0.00,"))
{
json = json.Replace("\"compare_at_price\":0.00", "\"compare_at_price\":\"\"");
var req = PrepareRequest($"variants/{productVariantId}.json");
using var content = new StringContent(json, Encoding.UTF8, "application/json");
await Task.Run(() => Thread.Sleep(500));
return (await ExecuteRequestAsync<ProductVariant>(req, HttpMethod.Put, default, content, "variant")
.ConfigureAwait(false)).Result;
}
return await base.UpdateAsync(productVariantId, variant);
}
catch (Exception)
{
return null;
}
}
}

Related

Get polygon from Azure Maps Search

I'm trying to use Azure.Maps.Search to give me a polygon for a result. For example, if I search for "Birmingham" I would like a result for that municipality with a collection of geopoints defining the boundary.
Is this possible?
var credential = new AzureKeyCredential("............");
var client = new MapsSearchClient(credential);
Response<SearchAddressResult> searchResult = await client.SearchAddressAsync(
query: "Birmingham",
options: new SearchAddressOptions
{
ExtendedPostalCodesFor=new SearchIndex[] { SearchIndex.PointAddresses },
CountryFilter = new string[] { "GB" },
Top = 1,
EntityType = GeographicEntity.Municipality
});
Yes, this is possible. The search address API will contain a DataSources.Geometries.ID value in the results that is the ID of the unique boundary for that result. You can take this ID and pass it into the GetPolygonsAsync API in the Azure.Maps.Search Nuget package.
using Azure;
using Azure.Maps.Search;
using Azure.Maps.Search.Models;
namespace AzureMapsTest
{
internal class Program
{
private const string MapsApiKey = "...........";
static async Task Main(string[] args)
{
var credential = new AzureKeyCredential(MapsApiKey);
var client = new MapsSearchClient(credential);
SearchAddressOptions singleSearchResultOptions = new SearchAddressOptions { Top = 1 };
Response<SearchAddressResult> searchResult =
await client.SearchAddressAsync(
"Ealing, London, England",
singleSearchResultOptions);
Response<PolygonResult> polygons =
await client.GetPolygonsAsync(new string[] { searchResult.Value.Results[0].DataSources.Geometry.Id });
Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(polygons.Value.Polygons));
}
}
}

How do I add an EPTimecardDetail record to a timecard?

I’m writing a customization to add records to a timecard and I’m trying to create a new record to add to the timecard. Using the logic in T230 I’m creating a variable and I’m being told by the compiler that EPTimecardDetail cannot be found.
I’ve added using PX.Objects.EP and PX.Objects.PM but I figure that if TimeCardMaint can be found then EPTimecardDetail should be able be found as well. I’ve included my using code as well but I think I’m missing something else.
using System;
using System.Collections;
using PX.Data;
using PX.Data.BQL.Fluent;
using PX.Data.BQL;
using PX.Objects.CS;
using PX.Objects.PM;
using PX.Objects.EP;
using PX.Objects.CR;
using PX.Objects.AR;
using PX.Objects.CT;
using PX.Objects.GL.FinPeriods;
using PX.TM;
using System.Collections.Generic;
namespace TimecardImport
{
public class NLTimecardLineEntry : PXGraph<NLTimecardLineEntry>
{
private static void DoPopulateTimeCard(Int32 employeeID, DateTime startDate, NLTimecardLine record)
{
TimeCardMaint graph = PXGraph.CreateInstance<TimeCardMaint>();
Int32 cardWeekID = PXWeekSelector2Attribute.GetWeekID(graph, startDate);
//look for an employee timecard with the current weekID
EPTimeCard card = PXSelectReadonly<EPTimeCard,
Where<EPTimeCard.employeeID, Equal<Required<EPTimeCard.employeeID>>,
And<EPTimeCard.weekId, Equal<Required<EPTimeCard.weekId>>>>>.SelectWindowed(graph, 0, 1, employeeID, cardWeekID);
if (card == null) //if a card was not found, create one
{
card = (EPTimeCard)graph.Document.Cache.CreateInstance();
card.WeekID = cardWeekID;
card.EmployeeID = employeeID;
card = graph.Document.Insert(card);
}
//at this point card is the card that we're going to work with
var detailLine = (EPTimecardDetail)graph.Activities.Cache.CreateCopy(
graph.Activities.Insert());
//detailLine.SetValueExt<detailLine.Date_Date>(record, record.InDate);
//detailLine.EarningTypeID = "RG";
//detailLine = graph.Activities.Update(detailLine);
graph.Save.Press();
}
}}
The error I'm getting is "The type or namespace name 'EPTimecardDetail' could not be found (are you missing a using directive or an assembly reference?)".
EPTimecardDetail is defined within PX.Objects.EP so I'm not sure why I'm having a problem there. Or, perhaps this is not the way to add records to the Details tab of the Employee Time Card screen?
For the namespace issue you can declare using PX.Object.EP and refer to the type as TimeCardMaint.EPTimecardDetail
Or you can declare using static PX.Objects.EP.TimeCardMaint and refer to the type as EPTimecardDetail
For inserting the record check the source code in file TimeCardMaint.cs There are examples on how to insert this DAC record.
Make sure the fields used for SQL joins like OrigNoteID and RefNoteID have the proper value (non null).
This example is from the Correct action in TimeCardMaint:
[PXUIField(DisplayName = Messages.Correct)]
[PXButton(ImageKey = PX.Web.UI.Sprite.Main.Release)]
public virtual IEnumerable Correct(PXAdapter adapter)
{
if (Document.Current != null)
{
EPTimeCard source = GetLastCorrection(Document.Current);
if (source.IsReleased != true)
return new EPTimeCard[] { source };
EPTimeCard newCard = (EPTimeCard)Document.Cache.CreateInstance();
newCard.WeekID = source.WeekID;
newCard.OrigTimeCardCD = source.TimeCardCD;
newCard = Document.Insert(newCard);
newCard.EmployeeID = source.EmployeeID;
PXNoteAttribute.CopyNoteAndFiles(Document.Cache, source, Document.Cache, newCard, true, true);
bool failed = false;
Dictionary<string, TimeCardSummaryCopiedInfo> summaryDescriptions = new Dictionary<string, TimeCardSummaryCopiedInfo>();
foreach (EPTimeCardSummary summary in Summary.View.SelectMultiBound(new object[] { source }))
{
string key = GetSummaryKey(summary);
if (!summaryDescriptions.ContainsKey(key))
{
string note = PXNoteAttribute.GetNote(Summary.Cache, summary);
var info = new TimeCardSummaryCopiedInfo(summary.Description, note);
summaryDescriptions.Add(key, info);
}
}
foreach (EPTimecardDetail act in TimecardActivities.View.SelectMultiBound(new object[] { source }))
{
EPTimecardDetail newActivity = PXCache<EPTimecardDetail>.CreateCopy(act);
newActivity.Released = false;
newActivity.Billed = false;
newActivity.NoteID = null;
newActivity.TimeCardCD = null;
newActivity.TimeSheetCD = null;
newActivity.OrigNoteID = act.NoteID; //relation between the original activity and the corrected one.
newActivity.Date = act.Date;
newActivity.Billed = false;
newActivity.SummaryLineNbr = null;
newActivity.NoteID = null;
newActivity.ContractCD = null;
isCreateCorrectionFlag = true;
try
{
newActivity = Activities.Insert(newActivity);
}
catch (PXSetPropertyException ex)
{
failed = true;
Activities.Cache.RaiseExceptionHandling<EPTimecardDetail.summary>(act, act.Summary, new PXSetPropertyException(ex.MessageNoPrefix, PXErrorLevel.RowError));
continue;
}
newActivity.TrackTime = act.TrackTime; //copy as is.
isCreateCorrectionFlag = false;
newActivity.ApprovalStatus = ActivityStatusAttribute.Completed;
newActivity.RefNoteID = act.NoteID == act.RefNoteID ? newActivity.NoteID : act.RefNoteID;
newActivity.ContractCD = act.ContractCD;
PXNoteAttribute.CopyNoteAndFiles(Activities.Cache, act, Activities.Cache, newActivity);
Activities.Cache.SetValue<EPTimecardDetail.isCorrected>(act, true);
Activities.Cache.SetStatus(act, PXEntryStatus.Updated);
}
if (failed)
{
throw new PXException(Messages.FailedToCreateCorrectionTC);
}
foreach (EPTimeCardItem item in Items.View.SelectMultiBound(new object[] { source }))
{
EPTimeCardItem record = Items.Insert();
record.ProjectID = item.ProjectID;
record.TaskID = item.TaskID;
record.Description = item.Description;
record.InventoryID = item.InventoryID;
record.CostCodeID = item.CostCodeID;
record.UOM = item.UOM;
record.Mon = item.Mon;
record.Tue = item.Tue;
record.Wed = item.Wed;
record.Thu = item.Thu;
record.Fri = item.Fri;
record.Sat = item.Sat;
record.Sun = item.Sun;
record.OrigLineNbr = item.LineNbr;//relation between the original activity and the corrected one.
}
foreach (EPTimeCardSummary summary in Summary.Select())
{
string key = GetSummaryKey(summary);
if (summaryDescriptions.ContainsKey(key))
{
PXNoteAttribute.SetNote(Summary.Cache, summary, summaryDescriptions[key].Note);
Summary.Cache.SetValue<EPTimeCardSummary.description>(summary, summaryDescriptions[key].Description);
}
}
return new EPTimeCard[] { newCard };
}
return adapter.Get();
}

Extend Umbraco back office search to search custom proerties not just title

What I would like to do is be able to type an custom property within the back office search. e.g. put the ISBN into the search field and have the results shown currently it always returns "no items found" as the search will only show results for the title node.
How do I enable the content search as seen in the image to search the data in the custom fields?
The data is in the internal index, I have checked the index is working and can see the result with "Examine Management" if I search via the custom data.
The solution is what I used to extend the search
https://dev.to/skttl/how-to-customize-searching-in-umbraco-list-views-1knk
Add a new file in the App_Code (SearchExtender)
using System.Linq;
using Examine;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Configuration;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Web;
using Umbraco.Web.Editors;
using Umbraco.Web.Models.ContentEditing;
namespace SearchExtender
{
public class CustomListViewSearchController : ContentController
{
public CustomListViewSearchController(PropertyEditorCollection propertyEditors, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper)
: base(propertyEditors, globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper)
{
}
public PagedResult<ContentItemBasic<ContentPropertyBasic>> GetChildrenCustom(int id, string includeProperties, int pageNumber = 0, int pageSize = 0, string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = "", string cultureName = "")
{
// get the parent node, and its doctype alias from the content cache
var parentNode = Services.ContentService.GetById(id);
var parentNodeDocTypeAlias = parentNode != null ? parentNode.ContentType.Alias : null;
// if the parent node is not "books", redirect to the core GetChildren() method
if (parentNode?.ContentType.Alias != "books")
{
return GetChildren(id, includeProperties, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter);
}
// if we can't get the InternalIndex, redirect to the core GetChildren() method, but log an error
if (!ExamineManager.Instance.TryGetIndex("InternalIndex", out IIndex index))
{
Logger.Error<CustomListViewSearchController>("Couldn't get InternalIndex for searching products in list view");
return GetChildren(id, includeProperties, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter);
}
// find children using Examine
// create search criteria
var searcher = index.GetSearcher();
var searchCriteria = searcher.CreateQuery();
var searchQuery = searchCriteria.Field("parentID", id);
if (!filter.IsNullOrWhiteSpace())
{
searchQuery = searchQuery.And().GroupedOr(new [] { "nodeName", "isbn" }, filter);
}
// do the search, but limit the results to the current page 👉 https://shazwazza.com/post/paging-with-examine/
// pageNumber is not zero indexed in this, so just multiply pageSize by pageNumber
var searchResults = searchQuery.Execute(pageSize * pageNumber);
// get the results on the current page
// pageNumber is not zero indexed in this, so subtract 1 from the pageNumber
var totalChildren = searchResults.TotalItemCount;
var pagedResultIds = searchResults.Skip((pageNumber > 0 ? pageNumber - 1 : 0) * pageSize).Select(x => x.Id).Select(x => int.Parse(x)).ToList();
var children = Services.ContentService.GetByIds(pagedResultIds).ToList();
if (totalChildren == 0)
{
return new PagedResult<ContentItemBasic<ContentPropertyBasic>>(0, 0, 0);
}
var pagedResult = new PagedResult<ContentItemBasic<ContentPropertyBasic>>(totalChildren, pageNumber, pageSize);
pagedResult.Items = children.Select(content =>
Mapper.Map<IContent, ContentItemBasic<ContentPropertyBasic>>(content))
.ToList(); // evaluate now
return pagedResult;
}
}
}
change requests for /umbraco/backoffice/UmbracoApi/Content/GetChildren (the default endpoint for child nodes), and change it to my newly created one, which is located at /umbraco/backoffice/api/CustomListViewSearch/GetChildrenCustom.
This is done easily by adding a js file containing an interceptor like this.
Add file to /App_Plugins/CustomListViewSearch/CustomListViewSearch.js
angular.module('umbraco.services').config([
'$httpProvider',
function ($httpProvider) {
$httpProvider.interceptors.push(function ($q) {
return {
'request': function (request) {
// Redirect any requests for the listview to our custom list view UI
if (request.url.indexOf("backoffice/UmbracoApi/Content/GetChildren?id=") > -1)
request.url = request.url.replace("backoffice/UmbracoApi/Content/GetChildren", "backoffice/api/CustomListViewSearch/GetChildrenCustom");
return request || $q.when(request);
}
};
});
}]);
a package.manifest file in my App_Plugins folder.
{
"javascript": [
"/App_Plugins/CustomListViewSearch/CustomListViewSearch.js"
]
}
If the node Alais isnot working make sure its set in the documnt type (far right on document type name)

Azure B2C security groups authorization

I'm trying to use example where I was able to update custom policies mentioned in this example and deployed REST API mentioned.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using AADB2C.RBAC.Sample.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using System.Net.Http;
namespace AADB2C.RBAC.Sample.Controllers
{
[Route("api/[controller]/[action]")]
public class IdentityController : Controller
{
private readonly AppSettingsModel AppSettings;
// Demo: Inject an instance of an AppSettingsModel class into the constructor of the consuming class,
// and let dependency injection handle the rest
public IdentityController(IOptions<AppSettingsModel> appSettings)
{
this.AppSettings = appSettings.Value;
}
[HttpPost(Name = "IsMemberOf")]
public async Task<ActionResult> IsMemberOf()
{
string input = null;
// If not data came in, then return
if (this.Request.Body == null)
{
return StatusCode((int)HttpStatusCode.Conflict, new B2CResponseModel("Request content is null", HttpStatusCode.Conflict));
}
//Read the input claims from the request body
using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8))
{
input = await reader.ReadToEndAsync();
}
//string input = Request.Content.ReadAsStringAsync().Result;
//string content = "";
//System.Web.HttpContext.Current.Request.InputStream.Position = 0;
//using (var reader = new StreamReader(
// Request.InputStream, System.Text.Encoding.UTF8, true, 4096, true))
//{
// content = reader.ReadToEnd();
//}
////Rest
//System.Web.HttpContext.Current.Request.InputStream.Position = 0;
// Check input content value
if (string.IsNullOrEmpty(input))
{
return StatusCode((int)HttpStatusCode.Conflict, new B2CResponseModel("Request content is empty", HttpStatusCode.Conflict));
}
// Convert the input string into InputClaimsModel object
InputClaimsModel inputClaims = InputClaimsModel.Parse(input);
if (inputClaims == null)
{
return StatusCode((int)HttpStatusCode.Conflict, new B2CResponseModel("Can not deserialize input claims", HttpStatusCode.Conflict));
}
if (string.IsNullOrEmpty(inputClaims.objectId))
{
return StatusCode((int)HttpStatusCode.Conflict, new B2CResponseModel("User 'objectId' is null or empty", HttpStatusCode.Conflict));
}
try
{
AzureADGraphClient azureADGraphClient = new AzureADGraphClient(this.AppSettings.Tenant, this.AppSettings.ClientId, this.AppSettings.ClientSecret);
// Demo: Get user's groups
GraphGroupsModel groups = await azureADGraphClient.GetUserGroup(inputClaims.objectId);
// Demo: Add the groups to string collections
List<string> groupsList = new List<string>();
foreach (var item in groups.value)
{
groupsList.Add(item.displayName);
}
// Demo: Set the output claims
OutputClaimsModel output = new OutputClaimsModel() { groups = groupsList };
// Demo: Check if user needs to be a member of a security group
if (!string.IsNullOrEmpty(inputClaims.onlyMembersOf))
{
List<string> onlyMembersOf = inputClaims.onlyMembersOf.ToLower().Split(',').ToList<string>();
bool isMemberOf = false;
foreach (var item in output.groups)
{
if (onlyMembersOf.Contains(item.ToLower()))
{
isMemberOf = true;
break;
}
}
// Demo: Throw error if user is not member of one of the security groups
if (isMemberOf == false)
{
return StatusCode((int)HttpStatusCode.Conflict, new B2CResponseModel("You are not authorized to sign-in to this application.", HttpStatusCode.Conflict));
}
}
// Demo: Return the groups collection
return Ok(output);
}
catch (Exception ex)
{
if (ex.Message.Contains("Request_ResourceNotFound"))
{
return StatusCode((int)HttpStatusCode.Conflict, new B2CResponseModel("Can not read user groups, user not found", HttpStatusCode.Conflict));
}
return StatusCode((int)HttpStatusCode.Conflict, new B2CResponseModel("Can not read user groups", HttpStatusCode.Conflict));
}
}
}
}
So I have reached point where custom policy uses REST POST method to get the groups details but this code mentioned doesn't provide me group details as it goes to catch statement and throws can not read user groups.The problem here is I cannot use localhost to run the Rest API and hit the API through custom policy ,I tried using proxy but it gives me bad request.
return StatusCode((int)HttpStatusCode.Conflict, new B2CResponseModel("Can not read user groups", HttpStatusCode.Conflict));
Any help or example would be very helpful
Web app code is in the source code folder in the linked repo.

Rewrite code for Automapper v5.0 to v4.0

Automapper v4.0 was very straight forward to use within a method, can someone help rewrite this for v5.0 please (specifically the Mapper code):
public IEnumerable<NotificationDto> GetNewNotifications()
{
var userId = User.Identity.GetUserId();
var notifications = _context.UserNotifications
.Where(un => un.UserId == userId && !un.IsRead)
.Select(un => un.Notification)
.Include(n => n.Gig.Artist)
.ToList();
Mapper.CreateMap<ApplicationUser, UserDto>();
Mapper.CreateMap<Gig, GigDto>();
Mapper.CreateMap<Notification, NotificationDto>();
return notifications.Select(Mapper.Map<Notification, NotificationDto>);
}
UPDATE:
It seems that EF Core doesn't project what AutoMapper is mapping with:
return notifications.Select(Mapper.Map<Notification, NotificationDto>);
But I do get results in Postman with the following code:
return notifications.Select(n => new NotificationDto()
{
DateTime = n.DateTime,
Gig = new GigDto()
{
Artist = new UserDto()
{
Id = n.Gig.Artist.Id,
Name = n.Gig.Artist.Name
},
DateTime = n.Gig.DateTime,
Id = n.Gig.Id,
IsCancelled = n.Gig.IsCancelled,
Venue = n.Gig.Venue
},
OriginalVenue = n.OriginalVenue,
OriginalDateTime = n.OriginalDateTime,
Type = n.Type
});
If you want to keep using static instance - the only change is in mapper initialization:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<ApplicationUser, UserDto>();
cfg.CreateMap<Gig, GigDto>();
cfg.CreateMap<Notification, NotificationDto>();
});
Also you should run this code only once per AppDomain (somewhere on startup for example) and not every time you calling GetNewNotifications.

Resources