Add Start and End time to ProductPart#713
Add Start and End time to ProductPart#713infofromca wants to merge 3 commits intoOrchardCMS:mainfrom
Conversation
sarahelsaig
left a comment
There was a problem hiding this comment.
As this is a new feature, please create a UI test to verify expired products.
There was a problem hiding this comment.
Interesting concept, but this is not a core product feature, so it shouldn't be in ProductPart
Move these fields into a new content part. As this is related to availability, it should be in OrchardCore.Commerce.Inventory. Also create a documentation file docs/features/*-part.md and link it in the mkdocs.yml.
| // Filter products by start and end time | ||
| var utcNow = DateTime.UtcNow; | ||
| query = query.With<ProductPartIndex>(index => | ||
| (index.StartTimeUtc == null || index.StartTimeUtc <= utcNow) && | ||
| (index.EndTimeUtc == null || index.EndTimeUtc >= utcNow)); |
There was a problem hiding this comment.
Create a new IProductListFilterProvider implementation and move this code into its BuildQueryAsync method.
| var utcNow = DateTime.UtcNow; | ||
| var contentItemIds = (await _session | ||
| .QueryIndex<ProductPartIndex>(index => index.Sku.IsIn(trimmedSkus)) | ||
| .QueryIndex<ProductPartIndex>(index => | ||
| index.Sku.IsIn(trimmedSkus) && | ||
| (index.StartTimeUtc == null || index.StartTimeUtc <= utcNow) && | ||
| (index.EndTimeUtc == null || index.EndTimeUtc >= utcNow)) |
There was a problem hiding this comment.
GetProductsAsync is used in a lot of places, in some cases fetching an expired product is valid.
I think you just want to disable expired products, right?
- To prevent adding to the cart, use
ShoppingCartEventsBaselike inInventoryShoppingCartEvents. - To prevent checkout, use
ICheckoutEventslike inInventoryCheckoutEvents
There was a problem hiding this comment.
This is not necessary. Content fields like DateTimeField already create an index on their own (in this case DateTimeFieldIndex) which you can join in your query. Please revert the changes in ProductPartIndex and ProductMigrations
|
I did the same in my customized code. I ended up creating a ProductRentalPart for these. Also needed to add a date range picker frontend option DateRangeProductAttributeValue.cs. using System;
using System.Globalization;
using OrchardCore.Commerce.Abstractions.ProductAttributeValues;
namespace OrchardCore.Commerce.ProductAttributeValues;
public class DateRangeProductAttributeValue : BaseProductAttributeValue<string>
{
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set; }
public string HalfDayMode { get; set; } // "am" | "pm" | null
public DateRangeProductAttributeValue(string attributeName, string value)
: base(attributeName, NormalizeToUtcString(value))
{
ParseDateRange(Value);
}
private static string NormalizeToUtcString(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
return string.Empty;
}
var parts = value.Split('|');
if (parts.Length < 2)
{
return value;
}
DateTime? start = ParseToUtc(parts[0]);
DateTime? end = ParseToUtc(parts[1]);
// Preserve optional am/pm suffix (3rd segment) for half-day bookings.
var suffix = parts.Length >= 3 ? parts[2].Trim().ToLowerInvariant() : null;
var validSuffix = suffix is "am" or "pm" ? suffix : null;
if (start.HasValue && end.HasValue)
{
// Store as ISO 8601 UTC without fractional seconds for compactness.
var normalized = string.Concat(
start.Value.ToString("yyyy-MM-dd'T'HH:mm:ss'Z'"),
"|",
end.Value.ToString("yyyy-MM-dd'T'HH:mm:ss'Z'")
);
return validSuffix is not null ? normalized + "|" + validSuffix : normalized;
}
return value;
}
private static DateTime? ParseToUtc(string s)
{
if (string.IsNullOrWhiteSpace(s)) return null;
// Exact date-only in local form: yyyy-MM-dd → treat as UTC midnight on that calendar day
if (DateTime.TryParseExact(s, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateOnly))
{
return new DateTime(dateOnly.Year, dateOnly.Month, dateOnly.Day, 0, 0, 0, DateTimeKind.Utc);
}
// Otherwise, parse and convert to UTC
if (DateTime.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out var dt))
{
return dt.Kind == DateTimeKind.Utc ? dt : dt.ToUniversalTime();
}
return null;
}
private void ParseDateRange(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
return;
}
var dates = value.Split('|');
if (dates.Length >= 2)
{
StartDate = ParseToUtc(dates[0]);
EndDate = ParseToUtc(dates[1]);
if (dates.Length >= 3)
{
var suffix = dates[2].Trim().ToLowerInvariant();
if (suffix is "am" or "pm")
{
HalfDayMode = suffix;
}
}
}
}
public override string Display(CultureInfo culture = null)
{
if (StartDate.HasValue && EndDate.HasValue)
{
var cultureToUse = culture ?? CultureInfo.CurrentCulture;
// Half-day booking: show single date with AM/PM label.
if (!string.IsNullOrEmpty(HalfDayMode))
{
var period = HalfDayMode == "am" ? "AM" : "PM";
return $"{StartDate.Value.ToString("d", cultureToUse)} ({period})";
}
return $"{StartDate.Value.ToString("d", cultureToUse)} - {EndDate.Value.ToString("d", cultureToUse)}";
}
return string.Empty;
}
} |
I don't think that's the same. The changes in this PR seem to be about offer availability (similar to the
This looks very cool. Would you be interested in adding it to OCC? Though if you do, instead of the |
|
I wanted to avoid timezone conversions by adding AM/PM only for now to be honest. But will surely do later on when I have a version that works with DateTime. |
Fix #684