Skip to content

Commit

Permalink
14.1.0: fix property value extractor does not work; add a toggle to i…
Browse files Browse the repository at this point in the history
…nclude tax to price (#13)

* move product feeds menu to the bottom of store settings menu

* Remove appsettings-schemas

* Fix property value extractor dropdown does not work

* Remove more schema.json

* add a toggle in the settings to add tax into the feed price; fix exception in multi media picker value extractor

* bump version number

* Remove dependency on commerce rc
  • Loading branch information
umbracotrd authored Oct 2, 2024
1 parent 550a8e5 commit b7c2109
Show file tree
Hide file tree
Showing 43 changed files with 197 additions and 52,056 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -477,3 +477,5 @@ $RECYCLE.BIN/
**/wwwroot/**
src/**/packages.lock.json
tests/**/packages.lock.json
**/appsettings-schema.**
**/umbraco-package-schema.json
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<PackageVersion Include="Umbraco.Cms" Version="[14.0.0, 15)" />
<PackageVersion Include="Umbraco.Cms.Web.Website" Version="[14.0.0, 15)" />
<PackageVersion Include="Umbraco.Cms.Web.BackOffice" Version="[14.0.0, 15)" />
<PackageVersion Include="Umbraco.Commerce" Version="[14.0.0-rc3, 15)" />
<PackageVersion Include="Umbraco.Commerce" Version="[14.0.0, 15)" />
<PackageVersion Include="coverlet.collector" Version="6.0.0" />
<PackageVersion Include="xunit" Version="2.6.6" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.5.6" />
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ With the Umbraco.Commerce.ProductFeeds installed you will be able to configure p


## Migrate from v13 and v0.5.5 to v14
- Migrate directly to v14.1.0 instead of v14.0.0. There's a bug that prevents you from changing property value extractor in the feed settings.
- Due to the change in schema of Product Document Type and Product Child Variant Types, you will need to manually edit your feed settings. Please go the the feed settings and find the [obsolete] properties and migrate them to the newer one
![image](https://github.com/user-attachments/assets/36d48973-11dc-49f2-b744-432152458419)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export default defineConfig({
client: '@hey-api/client-axios',
// client: '@hey-api/client-fetch',
// client: 'legacy/fetch',
input: 'http://localhost:43252/umbraco/swagger/ucproductfeeds/swagger.json',
input: 'http://localhost:44321/umbraco/swagger/ucproductfeeds/swagger.json',
output: {
path: 'src/generated/apis',
lint: 'eslint',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export type ProductFeedSettingReadModel = {
productChildVariantTypeIds: Array<(string)>;
feedRelativePath: string;
propertyNameMappings: Array<(PropertyAndNodeMapItem)>;
includeTaxInPrice: boolean;
};

export type ProductFeedSettingWriteModel = {
Expand All @@ -54,6 +55,7 @@ export type ProductFeedSettingWriteModel = {
propertyNameMappings: Array<(PropertyAndNodeMapItem)>;
productChildVariantTypeIds: Array<(string)>;
productDocumentTypeIds: Array<(string)>;
includeTaxInPrice: boolean;
};

export type ProductFeedType = 'GoogleMerchantCenter';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ export default {
'propProductRootIdLabel': 'Product Root',
'propProductRootIdDescription': 'Select the root for products. Only products under this root will be included in the feed.',

'propIncludeTaxInPriceLabel': 'Include Tax In Price',
'propIncludeTaxInPriceDescription': 'For countries like US and Canada, you need to show price without tax, for most other countries, you should show price with tax.',

'propPropNodeMappingLabel': 'Product Property And Feed Node Mapping',
'propPropNodeMappingDescription': 'Map between product property alias and the feed node under \\<item\\>.',

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ export const storeMenuManifests: UcManifestStoreMenuItem = {
entityType: listingWorkspaceManifest.meta.entityType,
icon: 'icon-rss',
},
weight: 9999,
weight: -1000,
};
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,8 @@ export class UcpfPropNodeMapper extends UmbLitElement {
selected: mapItem.valueExtractorName === extractor.value,
};
})}
.title=${mapItem.valueExtractorName ?? ''}
@change = ${(e: CustomEvent) => this.#onMapItemChange(e, mapItem.uiId)}>
@change=${(e: CustomEvent) => this.#onMapItemChange(e, mapItem.uiId)}
title=${mapItem.valueExtractorName ?? ''}>
</uui-select>
<uui-button
@click=${() => this.#onRemoveItemClick(mapItem.uiId)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { UmbWorkspaceViewElement } from '@umbraco-cms/backoffice/extension-registry';
import type { UUIEvent, UUIInputElement, UUISelectElement } from '@umbraco-cms/backoffice/external/uui';
import type { UUIBooleanInputElement, UUIEvent, UUIInputElement, UUISelectElement } from '@umbraco-cms/backoffice/external/uui';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import {
customElement,
Expand Down Expand Up @@ -89,6 +89,16 @@ export class UcpfDetailsWorkspaceViewElement
}
}

#onCheckboxChange(evt: UUIEvent) {
const checkboxEl = evt.target as UUIBooleanInputElement;
if (checkboxEl) {
this.#workspaceContext?.setModel({
...this._model!,
[checkboxEl.name]: checkboxEl.checked,
});
}
}

#onSelectElementChange(evt: UUIEvent) {
const selectEl = evt.target as UUISelectElement;
if (selectEl) {
Expand All @@ -103,15 +113,15 @@ export class UcpfDetailsWorkspaceViewElement
const selections = event.target.selection ?? [];
this.#workspaceContext?.setModel({
...this._model!,
productRootId: selections.join(','),
productRootId: selections.join(',') || undefined,
});

// this.#validateForm(); // TODO Dinh
}

#onProductDocumentTypeIdsChange(event: Event) {
const element = event.target as UmbInputDocumentTypeElement;
if (element.value) {
if (element) {
this.#workspaceContext?.setModel({
...this._model!,
productDocumentTypeIds: element.selection,
Expand All @@ -121,7 +131,7 @@ export class UcpfDetailsWorkspaceViewElement

#onProductChildVariantTypeIdsChange(event: Event) {
const element = event.target as UmbInputDocumentTypeElement;
if (element.value) {
if (element) {
this.#workspaceContext?.setModel({
...this._model!,
productChildVariantTypeIds: element.selection,
Expand All @@ -133,7 +143,7 @@ export class UcpfDetailsWorkspaceViewElement
return () => {
this.#workspaceContext?.setModel({
...this._model!,
[propName]: '',
[propName]: undefined,
});
};
}
Expand Down Expand Up @@ -212,6 +222,7 @@ export class UcpfDetailsWorkspaceViewElement
name='productDocumentTypeIds'
slot='editor'
@change=${this.#onProductDocumentTypeIdsChange}
?documentTypesOnly=${true}
.selection=${this._model?.productDocumentTypeIds ?? []}
></umb-input-document-type>
</umb-property-layout>
Expand Down Expand Up @@ -259,19 +270,30 @@ export class UcpfDetailsWorkspaceViewElement
${this._model?.productRootId && html`<uui-button class='ucpf-clearPropButton' slot='editor' look='secondary' @click=${this.#onClearInputClick('productRootId')} label=${this.localize.term('general_remove')}></uui-button>`}
</umb-property-layout>
<umb-property-layout
label=${this.localize.term('ucProductFeeds_propIncludeTaxInPriceLabel')}
description=${this.localize.term('ucProductFeeds_propIncludeTaxInPriceDescription')}
>
<uui-toggle
slot="editor"
label=""
name="includeTaxInPrice"
@change=${this.#onCheckboxChange}
?checked=${!!this._model?.includeTaxInPrice}
></uui-toggle>
</umb-property-layout>
<umb-property-layout
label=${this.localize.term('ucProductFeeds_propPropNodeMappingLabel')}
description=${this.localize.term('ucProductFeeds_propPropNodeMappingDescription')}
?mandatory=${true}
>
<ucpf-property-node-mapper
slot='editor'
.mapItems=${this._model?.propertyNameMappings ?? []}
.propertyValueExtractorOptions=${this._propertyValueExtractorOptions}
@change=${this.#onPropNodeMapperChange}
></ucpf-property-node-mapper>
</umb-property-layout>
</uc-stack>
</uui-box>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ export class UcpfListCollectionViewTableElement extends UmbLitElement {
this.observe(
this.#collectionContext.items,
(collectionItems) => {
console.log('collectionItems', collectionItems);
this.#createTableItems(collectionItems);
},
'umbCollectionItemsObserver',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public XmlDocument GenerateFeed(ProductFeedSettingReadModel feedSetting)
// add url to the main product
AddUrlNode(itemNode, product);

AddPriceNode(itemNode, feedSetting.StoreId, childVariant, null);
AddPriceNode(itemNode, feedSetting, childVariant, null);

// group variant into the same parent id
PropertyAndNodeMapItem idPropMap = feedSetting.PropertyNameMappings.FirstOrDefault(x => x.NodeName.Equals("g:id", StringComparison.OrdinalIgnoreCase)) ?? throw new IdPropertyNodeMappingNotFoundException();
Expand All @@ -115,12 +115,12 @@ public XmlDocument GenerateFeed(ProductFeedSettingReadModel feedSetting)
{
XmlElement itemNode = NewItemNode(feedSetting, channel, complexVariant.Content, product);

// add url to the main product
// add a url to the main product
AddUrlNode(itemNode, product);

AddPriceNode(itemNode, feedSetting.StoreId, product, complexVariant.Content);
AddPriceNode(itemNode, feedSetting, product, complexVariant.Content);

// group variant into the same parent id
// group variant under the main product id
PropertyAndNodeMapItem idPropMap = feedSetting.PropertyNameMappings.FirstOrDefault(x => x.NodeName.Equals("g:id", StringComparison.OrdinalIgnoreCase)) ?? throw new IdPropertyNodeMappingNotFoundException();
AddItemGroupNode(itemNode, product.GetPropertyValue<object?>(idPropMap.PropertyAlias, product)?.ToString() ?? string.Empty);

Expand All @@ -138,6 +138,14 @@ public XmlDocument GenerateFeed(ProductFeedSettingReadModel feedSetting)
return doc;
}

/// <summary>
/// Create a new node for the one, in this case, each product/variant is one &lt;item&gt; node.
/// </summary>
/// <param name="feedSetting"></param>
/// <param name="channel"></param>
/// <param name="variant"></param>
/// <param name="mainProduct"></param>
/// <returns></returns>
private XmlElement NewItemNode(ProductFeedSettingReadModel feedSetting, XmlElement channel, IPublishedElement variant, IPublishedElement? mainProduct)
{
XmlElement itemNode = channel.OwnerDocument.CreateElement("item");
Expand Down Expand Up @@ -165,7 +173,7 @@ private XmlElement NewItemNode(ProductFeedSettingReadModel feedSetting, XmlEleme
if (mainProduct == null) // when the variant is the main product itself
{
AddUrlNode(itemNode, (IPublishedContent)variant);
AddPriceNode(itemNode, feedSetting.StoreId, variant, null);
AddPriceNode(itemNode, feedSetting, variant, null);
}

return itemNode;
Expand All @@ -178,15 +186,29 @@ private static void AddItemGroupNode(XmlElement itemNode, string groupId)
itemNode.AppendChild(availabilityNode);
}

/// <summary>
/// Add a &lt;url&gt; node under the provided &lt;item&gt; node.
/// </summary>
/// <param name="itemNode"></param>
/// <param name="product"></param>
private static void AddUrlNode(XmlElement itemNode, IPublishedContent product)
{
XmlElement linkNode = itemNode.OwnerDocument.CreateElement("g:link", GoogleXmlNamespaceUri);
linkNode.InnerText = product.Url(mode: UrlMode.Absolute);
itemNode.AppendChild(linkNode);
}

private void AddPriceNode(XmlElement itemNode, Guid storeId, IPublishedElement product, IPublishedElement? complexVariant)
/// <summary>
/// Add a &lt;price&gt; node under the provided &lt;item&gt; node.
/// </summary>
/// <param name="itemNode"></param>
/// <param name="feedSetting"></param>
/// <param name="product"></param>
/// <param name="complexVariant"></param>
/// <exception cref="NotImplementedException"></exception>
private void AddPriceNode(XmlElement itemNode, ProductFeedSettingReadModel feedSetting, IPublishedElement product, IPublishedElement? complexVariant)
{
Guid storeId = feedSetting.StoreId;
IProductSnapshot? productSnapshot = complexVariant == null ?
_commerceApi.GetProduct(storeId, product.Key.ToString(), Thread.CurrentThread.CurrentCulture.Name)
: _commerceApi.GetProduct(storeId, product.Key.ToString(), complexVariant.Key.ToString(), Thread.CurrentThread.CurrentCulture.Name);
Expand All @@ -200,10 +222,19 @@ private void AddPriceNode(XmlElement itemNode, Guid storeId, IPublishedElement p
XmlElement priceNode = itemNode.OwnerDocument.CreateElement("g:price", GoogleXmlNamespaceUri);
Attempt<Price> calculatePriceAttempt = productSnapshot.TryCalculatePrice();
Price calculatedPrice = calculatePriceAttempt.Success ? calculatePriceAttempt.Result! : throw new NotImplementedException("Failed to calculate the price");
priceNode.InnerText = $"{calculatedPrice.WithTax.ToString("0.00", CultureInfo.InvariantCulture)} {_currencyService.GetCurrency(calculatedPrice.CurrencyId).Code}";
decimal priceForShow = feedSetting.IncludeTaxInPrice ? calculatedPrice.WithTax : calculatedPrice.WithoutTax;
priceNode.InnerText = $"{priceForShow.ToString("0.00", CultureInfo.InvariantCulture)} {_currencyService.GetCurrency(calculatedPrice.CurrencyId).Code}";
itemNode.AppendChild(priceNode);
}

/// <summary>
/// Add image nodes under the provided &lt;item&gt; node.
/// </summary>
/// <param name="itemNode"></param>
/// <param name="valueExtractorName"></param>
/// <param name="propertyAlias"></param>
/// <param name="product"></param>
/// <param name="mainProduct"></param>
private void AddImageNodes(XmlElement itemNode, string valueExtractorName, string propertyAlias, IPublishedElement product, IPublishedElement? mainProduct)
{
IMultipleValuePropertyExtractor multipleValuePropertyExtractor = _multipleValuePropertyExtractorFactory.GetExtractor(valueExtractorName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
namespace Umbraco.Commerce.ProductFeeds.Core.Features.FeedSettings.Application
{

#pragma warning disable CA2227 // Collection properties should be read only
public class ProductFeedSettingReadModel
{
public Guid Id { get; set; }
Expand All @@ -30,7 +29,8 @@ public class ProductFeedSettingReadModel

public required string FeedRelativePath { get; set; }

public ICollection<PropertyAndNodeMapItem> PropertyNameMappings { get; set; } = [];
public ICollection<PropertyAndNodeMapItem> PropertyNameMappings { get; init; } = [];

public bool IncludeTaxInPrice { get; set; }
}
#pragma warning restore CA2227 // Collection properties should be read only
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

namespace Umbraco.Commerce.ProductFeeds.Core.FeedSettings.Application
{
#pragma warning disable CA2227 // Collection properties should be read only
public class ProductFeedSettingWriteModel
{
public Guid? Id { get; set; }
Expand All @@ -25,12 +24,12 @@ public class ProductFeedSettingWriteModel
[Obsolete("Will be removed in v15. Use ProductChildVariantTypeIds instead")]
public string? ProductChildVariantTypeAlias { get; set; }

public ICollection<PropertyAndNodeMapItem> PropertyNameMappings { get; set; } = [];
public ICollection<PropertyAndNodeMapItem> PropertyNameMappings { get; init; } = [];

public IEnumerable<Guid> ProductChildVariantTypeIds { get; set; } = [];

public ICollection<Guid> ProductDocumentTypeIds { get; set; } = [];
public ICollection<Guid> ProductDocumentTypeIds { get; init; } = [];

public bool IncludeTaxInPrice { get; set; }
}
#pragma warning restore CA2227 // Collection properties should be read only
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@ namespace Umbraco.Commerce.ProductFeeds.Core.Features.FeedSettings.Application
{
public class PropertyAndNodeMapItem
{
/// <summary>
/// Product property alias.
/// </summary>
public required string PropertyAlias { get; set; }

/// <summary>
/// Xml node name.
/// </summary>
public required string NodeName { get; set; }

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Commerce.ProductFeeds.Core.PropertyValueExtractors.Application;
using Umbraco.Commerce.ProductFeeds.Extensions;
Expand All @@ -18,7 +19,7 @@ public ICollection<string> Extract(IPublishedElement content, string propertyAli
return new List<string>();
}

List<IPublishedContent>? medias = content.GetPropertyValue<List<IPublishedContent>>(propertyAlias, fallbackElement);
List<MediaWithCrops>? medias = content.GetPropertyValue<List<MediaWithCrops>>(propertyAlias, fallbackElement);
if (medias == null || medias.Count == 0)
{
return new List<string>();
Expand Down
Loading

0 comments on commit b7c2109

Please sign in to comment.