diff --git a/src/PowderCoating.Application/DTOs/Inventory/InventoryDtos.cs b/src/PowderCoating.Application/DTOs/Inventory/InventoryDtos.cs index a2d59e2..846703d 100644 --- a/src/PowderCoating.Application/DTOs/Inventory/InventoryDtos.cs +++ b/src/PowderCoating.Application/DTOs/Inventory/InventoryDtos.cs @@ -258,6 +258,8 @@ public class PowderUsageLogDto public decimal VarianceLbs { get; set; } public DateTime RecordedAt { get; set; } public string? Notes { get; set; } + /// Set only for rows synthesized from a scan-based InventoryTransaction (no PowderUsageLog record). + public int? SourceTransactionId { get; set; } } public class InventoryLedgerViewModel diff --git a/src/PowderCoating.Application/DTOs/Job/JobDtos.cs b/src/PowderCoating.Application/DTOs/Job/JobDtos.cs index b8b8196..83ff0ed 100644 --- a/src/PowderCoating.Application/DTOs/Job/JobDtos.cs +++ b/src/PowderCoating.Application/DTOs/Job/JobDtos.cs @@ -404,9 +404,8 @@ public class JobTimeEntryDto { public int Id { get; set; } public int JobId { get; set; } - public int ShopWorkerId { get; set; } + public string? UserId { get; set; } public string WorkerName { get; set; } = string.Empty; - public string WorkerRole { get; set; } = string.Empty; public DateTime WorkDate { get; set; } public decimal HoursWorked { get; set; } public string? Stage { get; set; } @@ -417,7 +416,7 @@ public class JobTimeEntryDto public class CreateJobTimeEntryDto { public int JobId { get; set; } - public int ShopWorkerId { get; set; } + public string UserId { get; set; } = string.Empty; public DateTime WorkDate { get; set; } public decimal HoursWorked { get; set; } public string? Stage { get; set; } @@ -427,7 +426,7 @@ public class CreateJobTimeEntryDto public class UpdateJobTimeEntryDto { public int Id { get; set; } - public int ShopWorkerId { get; set; } + public string UserId { get; set; } = string.Empty; public DateTime WorkDate { get; set; } public decimal HoursWorked { get; set; } public string? Stage { get; set; } diff --git a/src/PowderCoating.Application/Interfaces/IPlatformSettingsService.cs b/src/PowderCoating.Application/Interfaces/IPlatformSettingsService.cs index 06729d1..eb6d938 100644 --- a/src/PowderCoating.Application/Interfaces/IPlatformSettingsService.cs +++ b/src/PowderCoating.Application/Interfaces/IPlatformSettingsService.cs @@ -6,6 +6,7 @@ public interface IPlatformSettingsService { Task GetAsync(string key); Task GetBoolAsync(string key, bool defaultValue = false); + Task GetIntAsync(string key, int defaultValue); Task SetAsync(string key, string? value, string? updatedBy = null); Task> GetAllAsync(); } diff --git a/src/PowderCoating.Application/Mappings/JobProfile.cs b/src/PowderCoating.Application/Mappings/JobProfile.cs index 2e206bb..fb4ab4a 100644 --- a/src/PowderCoating.Application/Mappings/JobProfile.cs +++ b/src/PowderCoating.Application/Mappings/JobProfile.cs @@ -67,8 +67,8 @@ public class JobProfile : Profile // JobTimeEntry → JobTimeEntryDto CreateMap() - .ForMember(dest => dest.WorkerName, opt => opt.MapFrom(src => src.Worker != null ? src.Worker.Name : string.Empty)) - .ForMember(dest => dest.WorkerRole, opt => opt.MapFrom(src => src.Worker != null ? FormatEnumName(src.Worker.Role.ToString()) : string.Empty)); + .ForMember(dest => dest.WorkerName, opt => opt.MapFrom(src => + src.UserDisplayName ?? (src.Worker != null ? src.Worker.Name : string.Empty))); // CreateJobDto to Job CreateMap() diff --git a/src/PowderCoating.Core/Entities/JobTimeEntry.cs b/src/PowderCoating.Core/Entities/JobTimeEntry.cs index 739cfc4..be96cc6 100644 --- a/src/PowderCoating.Core/Entities/JobTimeEntry.cs +++ b/src/PowderCoating.Core/Entities/JobTimeEntry.cs @@ -3,7 +3,9 @@ namespace PowderCoating.Core.Entities; public class JobTimeEntry : BaseEntity { public int JobId { get; set; } - public int ShopWorkerId { get; set; } + public int? ShopWorkerId { get; set; } // legacy — kept for entries created before user migration + public string? UserId { get; set; } // FK to AspNetUsers + public string? UserDisplayName { get; set; } // snapshot of worker name at entry creation time public DateTime WorkDate { get; set; } public decimal HoursWorked { get; set; } public string? Stage { get; set; } // e.g. "Sandblasting", "Coating", "Masking" — free text @@ -11,5 +13,5 @@ public class JobTimeEntry : BaseEntity // Navigation public virtual Job Job { get; set; } = null!; - public virtual ShopWorker Worker { get; set; } = null!; + public virtual ShopWorker? Worker { get; set; } // nullable — only populated for legacy entries } diff --git a/src/PowderCoating.Core/Entities/PlatformSettingKeys.cs b/src/PowderCoating.Core/Entities/PlatformSettingKeys.cs index db1f0f6..852ff62 100644 --- a/src/PowderCoating.Core/Entities/PlatformSettingKeys.cs +++ b/src/PowderCoating.Core/Entities/PlatformSettingKeys.cs @@ -15,4 +15,6 @@ public static class PlatformSettingKeys public const string MaxTenants = "MaxTenants"; public const string SmsEnabled = "SmsEnabled"; public const string AiCatalogPriceCheckEnabled = "AiCatalogPriceCheckEnabled"; + public const string GracePeriodDays = "GracePeriodDays"; + public const string GracePeriodAppliesToTrials = "GracePeriodAppliesToTrials"; } diff --git a/src/PowderCoating.Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs b/src/PowderCoating.Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs index 123fead..5a86b20 100644 --- a/src/PowderCoating.Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/PowderCoating.Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs @@ -5000,7 +5000,7 @@ namespace PowderCoating.Infrastructure.Migrations b.Property("Notes") .HasColumnType("nvarchar(max)"); - b.Property("ShopWorkerId") + b.Property("ShopWorkerId") .HasColumnType("int"); b.Property("Stage") @@ -5012,6 +5012,12 @@ namespace PowderCoating.Infrastructure.Migrations b.Property("UpdatedBy") .HasColumnType("nvarchar(max)"); + b.Property("UserDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("nvarchar(max)"); + b.Property("WorkDate") .HasColumnType("datetime2"); @@ -6028,7 +6034,7 @@ namespace PowderCoating.Infrastructure.Migrations { Id = 1, CompanyId = 0, - CreatedAt = new DateTime(2026, 5, 3, 20, 30, 44, 955, DateTimeKind.Utc).AddTicks(5184), + CreatedAt = new DateTime(2026, 5, 5, 23, 10, 14, 763, DateTimeKind.Utc).AddTicks(8603), Description = "Standard pricing for regular customers", DiscountPercent = 0m, IsActive = true, @@ -6039,7 +6045,7 @@ namespace PowderCoating.Infrastructure.Migrations { Id = 2, CompanyId = 0, - CreatedAt = new DateTime(2026, 5, 3, 20, 30, 44, 955, DateTimeKind.Utc).AddTicks(5189), + CreatedAt = new DateTime(2026, 5, 5, 23, 10, 14, 763, DateTimeKind.Utc).AddTicks(8610), Description = "5% discount for preferred customers", DiscountPercent = 5m, IsActive = true, @@ -6050,7 +6056,7 @@ namespace PowderCoating.Infrastructure.Migrations { Id = 3, CompanyId = 0, - CreatedAt = new DateTime(2026, 5, 3, 20, 30, 44, 955, DateTimeKind.Utc).AddTicks(5191), + CreatedAt = new DateTime(2026, 5, 5, 23, 10, 14, 763, DateTimeKind.Utc).AddTicks(8612), Description = "10% discount for premium customers", DiscountPercent = 10m, IsActive = true, @@ -8694,9 +8700,7 @@ namespace PowderCoating.Infrastructure.Migrations b.HasOne("PowderCoating.Core.Entities.ShopWorker", "Worker") .WithMany("TimeEntries") - .HasForeignKey("ShopWorkerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + .HasForeignKey("ShopWorkerId"); b.Navigation("Job"); diff --git a/src/PowderCoating.Infrastructure/Repositories/InventoryTransactionRepository.cs b/src/PowderCoating.Infrastructure/Repositories/InventoryTransactionRepository.cs index 84d965a..e344696 100644 --- a/src/PowderCoating.Infrastructure/Repositories/InventoryTransactionRepository.cs +++ b/src/PowderCoating.Infrastructure/Repositories/InventoryTransactionRepository.cs @@ -26,6 +26,7 @@ public class InventoryTransactionRepository : Repository, .Include(t => t.InventoryItem) .Include(t => t.PurchaseOrder) .Include(t => t.Job) + .ThenInclude(j => j!.Customer) .Where(t => !t.IsDeleted); if (itemId.HasValue) diff --git a/src/PowderCoating.Infrastructure/Services/InventoryAiLookupService.cs b/src/PowderCoating.Infrastructure/Services/InventoryAiLookupService.cs index b760b96..497ca52 100644 --- a/src/PowderCoating.Infrastructure/Services/InventoryAiLookupService.cs +++ b/src/PowderCoating.Infrastructure/Services/InventoryAiLookupService.cs @@ -834,6 +834,10 @@ Rules: // machine-readable price, SKU, and product info that would otherwise be lost. var structuredData = ExtractJsonLdData(html); + // Extract visible price text BEFORE stripping HTML as a fallback when + // JSON-LD is absent or incomplete (e.g. JS-rendered stores). + var htmlPriceSnippet = ExtractHtmlPriceSnippet(html); + // Remove script/style blocks html = System.Text.RegularExpressions.Regex.Replace( html, @"<(script|style)[^>]*>[\s\S]*?", "", @@ -851,10 +855,11 @@ Rules: if (text.Length > maxChars) text = text[..maxChars] + "…"; - // Prepend structured data + document links — Claude treats these as high-confidence + // Prepend structured data + document links + price fallback — Claude treats these as high-confidence var header = new StringBuilder(); - if (!string.IsNullOrWhiteSpace(structuredData)) header.Append(structuredData); - if (!string.IsNullOrWhiteSpace(docLinks)) header.Append(docLinks); + if (!string.IsNullOrWhiteSpace(structuredData)) header.Append(structuredData); + if (!string.IsNullOrWhiteSpace(htmlPriceSnippet)) header.Append(htmlPriceSnippet); + if (!string.IsNullOrWhiteSpace(docLinks)) header.Append(docLinks); if (header.Length > 0) text = header + "\n" + text; _logger.LogInformation("Fetched {Chars} chars from {Url} (structured data: {HasData}, image: {HasImage})", @@ -897,6 +902,46 @@ Rules: return null; } + /// + /// Scans raw HTML for visible price elements — common WooCommerce/Shopify price class + /// names and itemprop="price" microdata — and returns them as a "[Page Price]" header + /// line. This runs on the full, untruncated HTML before tag stripping so prices that + /// would fall past the 3,500-char page-text cutoff are still surfaced to Claude. + /// Only used when JSON-LD structured data didn't already yield a price. + /// + private static string? ExtractHtmlPriceSnippet(string html) + { + // itemprop="price" microdata (e.g. 12.99) + var microprice = System.Text.RegularExpressions.Regex.Match( + html, + @"itemprop=[""']price[""'][^>]*content=[""']([0-9]+\.?[0-9]*)[""']|" + + @"itemprop=[""']price[""'][^>]*>[\s]*\$?([0-9]+\.?[0-9]*)", + System.Text.RegularExpressions.RegexOptions.IgnoreCase); + if (microprice.Success) + { + var val = (microprice.Groups[1].Value.Trim().Length > 0 + ? microprice.Groups[1].Value + : microprice.Groups[2].Value).Trim(); + if (!string.IsNullOrEmpty(val)) + return $"[Page Price] Price found in page microdata: ${val}\n"; + } + + // WooCommerce / common e-commerce price class names + var classPricePattern = System.Text.RegularExpressions.Regex.Match( + html, + @"class=[""'][^""']*(?:price|woocommerce-Price-amount|product-price|bdi)[^""']*[""'][^>]*>" + + @"[\s\S]{0,60}?\$\s*([0-9]+\.[0-9]{2})", + System.Text.RegularExpressions.RegexOptions.IgnoreCase); + if (classPricePattern.Success) + { + var val = classPricePattern.Groups[1].Value.Trim(); + if (!string.IsNullOrEmpty(val)) + return $"[Page Price] Price found in page HTML: ${val}\n"; + } + + return null; + } + /// /// Scans raw HTML for anchor tags linking to SDS or TDS documents and returns them as /// "[Structured Data]" lines that Claude can read and echo back in its JSON response. @@ -959,6 +1004,12 @@ Rules: /// Extracts product name, SKU, and price from JSON-LD structured data blocks. /// Many e-commerce sites (Shopify, WooCommerce, etc.) embed this in the page HTML /// even when the visible price is rendered by JavaScript. + /// + /// Handles three common JSON-LD shapes: + /// - Single Product object: {"@type":"Product",...} + /// - Top-level array: [{"@type":"Product",...},...] + /// - WooCommerce/Yoast @graph wrapper: {"@graph":[{"@type":"Product",...},...]} + /// /// private static string? ExtractJsonLdData(string html) { @@ -976,12 +1027,7 @@ Rules: using var doc = JsonDocument.Parse(jsonText); var root = doc.RootElement; - // Handle both single-object and @graph array - IEnumerable nodes = root.ValueKind == JsonValueKind.Array - ? root.EnumerateArray() - : new[] { root }; - - foreach (var node in nodes) + foreach (var node in FlattenJsonLdNodes(root)) { if (!node.TryGetProperty("@type", out var typeEl)) continue; @@ -1013,6 +1059,34 @@ Rules: return sb.Length > 0 ? sb.ToString() : null; } + /// + /// Recursively flattens a JSON-LD element into individual nodes for processing. + /// Handles a top-level array, a single object, and the WooCommerce/Yoast @graph + /// wrapper pattern where the root object has no @type but contains an "@graph" array. + /// + private static IEnumerable FlattenJsonLdNodes(JsonElement el) + { + if (el.ValueKind == JsonValueKind.Array) + { + foreach (var item in el.EnumerateArray()) + foreach (var n in FlattenJsonLdNodes(item)) + yield return n; + } + else if (el.ValueKind == JsonValueKind.Object) + { + // @graph wrapper: {"@graph": [...]} — recurse into the array + if (el.TryGetProperty("@graph", out var graph) && graph.ValueKind == JsonValueKind.Array) + { + foreach (var n in FlattenJsonLdNodes(graph)) + yield return n; + } + else + { + yield return el; + } + } + } + /// /// Dispatches JSON-LD offer extraction for both the single-object form /// ("offers": { ... }) and the array form ("offers": [ ... ]) diff --git a/src/PowderCoating.Infrastructure/Services/PlatformSettingsService.cs b/src/PowderCoating.Infrastructure/Services/PlatformSettingsService.cs index 7455549..9115ef0 100644 --- a/src/PowderCoating.Infrastructure/Services/PlatformSettingsService.cs +++ b/src/PowderCoating.Infrastructure/Services/PlatformSettingsService.cs @@ -52,6 +52,17 @@ public class PlatformSettingsService : IPlatformSettingsService return bool.TryParse(value, out var result) ? result : defaultValue; } + /// + /// Reads a platform setting as an integer. Returns when + /// the key is missing or the stored value cannot be parsed as an integer. + /// + public async Task GetIntAsync(string key, int defaultValue) + { + var value = await GetAsync(key); + if (value == null) return defaultValue; + return int.TryParse(value, out var result) ? result : defaultValue; + } + /// /// Creates or updates the platform setting identified by . /// Records (typically the SuperAdmin's username) and diff --git a/src/PowderCoating.Infrastructure/Services/SubscriptionService.cs b/src/PowderCoating.Infrastructure/Services/SubscriptionService.cs index 612986d..f96d680 100644 --- a/src/PowderCoating.Infrastructure/Services/SubscriptionService.cs +++ b/src/PowderCoating.Infrastructure/Services/SubscriptionService.cs @@ -1,4 +1,5 @@ using Microsoft.EntityFrameworkCore; +using PowderCoating.Application.Interfaces; using PowderCoating.Core.Entities; using PowderCoating.Core.Enums; using PowderCoating.Core.Interfaces; @@ -27,11 +28,13 @@ public class SubscriptionService : ISubscriptionService { private readonly IUnitOfWork _unitOfWork; private readonly ApplicationDbContext _context; + private readonly IPlatformSettingsService _platformSettings; - public SubscriptionService(IUnitOfWork unitOfWork, ApplicationDbContext context) + public SubscriptionService(IUnitOfWork unitOfWork, ApplicationDbContext context, IPlatformSettingsService platformSettings) { _unitOfWork = unitOfWork; _context = context; + _platformSettings = platformSettings; } /// @@ -203,7 +206,8 @@ public class SubscriptionService : ISubscriptionService if (daysUntil == null || daysUntil > 0) return SubscriptionStatus.Active; - if (daysUntil >= -AppConstants.SubscriptionConstants.GracePeriodDays) + var graceDays = await GetEffectiveGracePeriodDaysAsync(company); + if (daysUntil >= -graceDays) return SubscriptionStatus.GracePeriod; return SubscriptionStatus.Expired; @@ -226,6 +230,25 @@ public class SubscriptionService : ISubscriptionService return (int)(expiry - today).TotalDays; } + /// + /// Returns the effective grace period in days for a company. Trial companies (no Stripe subscription) + /// get 0 days unless the GracePeriodAppliesToTrials platform setting is explicitly enabled. + /// Paid companies always use the configured GracePeriodDays value. + /// + private async Task GetEffectiveGracePeriodDaysAsync(Company company) + { + var isTrial = string.IsNullOrEmpty(company.StripeSubscriptionId); + if (isTrial) + { + var appliesToTrials = await _platformSettings.GetBoolAsync( + PlatformSettingKeys.GracePeriodAppliesToTrials, defaultValue: false); + if (!appliesToTrials) return 0; + } + return await _platformSettings.GetIntAsync( + PlatformSettingKeys.GracePeriodDays, + AppConstants.SubscriptionConstants.GracePeriodDays); + } + /// /// Returns the total catalog item count (including soft-deleted) and the plan maximum. /// Returns a max of -1 when the plan config is absent (unlimited by default). diff --git a/src/PowderCoating.Shared/Constants/AppConstants.cs b/src/PowderCoating.Shared/Constants/AppConstants.cs index a549144..ab94f7b 100644 --- a/src/PowderCoating.Shared/Constants/AppConstants.cs +++ b/src/PowderCoating.Shared/Constants/AppConstants.cs @@ -2,7 +2,7 @@ namespace PowderCoating.Shared.Constants; public static class AppConstants { - public const string ApplicationName = "Powder Coating Management System"; + public const string ApplicationName = "Powder Coating Logix"; public const string Version = "1.0.0"; /// Set to true to enable SMS features throughout the UI. diff --git a/src/PowderCoating.Web/BackgroundServices/SubscriptionExpiryBackgroundService.cs b/src/PowderCoating.Web/BackgroundServices/SubscriptionExpiryBackgroundService.cs index e44c0e8..6b0e0ea 100644 --- a/src/PowderCoating.Web/BackgroundServices/SubscriptionExpiryBackgroundService.cs +++ b/src/PowderCoating.Web/BackgroundServices/SubscriptionExpiryBackgroundService.cs @@ -80,6 +80,12 @@ public class SubscriptionExpiryBackgroundService : BackgroundService var db = scope.ServiceProvider.GetRequiredService(); var emailService = scope.ServiceProvider.GetRequiredService(); var adminNotification = scope.ServiceProvider.GetRequiredService(); + var platformSettings = scope.ServiceProvider.GetRequiredService(); + var gracePeriodDays = await platformSettings.GetIntAsync( + PlatformSettingKeys.GracePeriodDays, + AppConstants.SubscriptionConstants.GracePeriodDays); + var gracePeriodAppliesToTrials = await platformSettings.GetBoolAsync( + PlatformSettingKeys.GracePeriodAppliesToTrials, defaultValue: false); var today = DateTime.UtcNow.Date; @@ -100,7 +106,9 @@ public class SubscriptionExpiryBackgroundService : BackgroundService foreach (var company in companies) { if (ct.IsCancellationRequested) break; - await ProcessCompanyAsync(db, emailService, adminNotification, company, today, ct); + var isTrial = string.IsNullOrEmpty(company.StripeSubscriptionId); + var effectiveGraceDays = isTrial && !gracePeriodAppliesToTrials ? 0 : gracePeriodDays; + await ProcessCompanyAsync(db, emailService, adminNotification, company, today, effectiveGraceDays, ct); } await db.SaveChangesAsync(ct); @@ -124,10 +132,10 @@ public class SubscriptionExpiryBackgroundService : BackgroundService IAdminNotificationService adminNotification, PowderCoating.Core.Entities.Company company, DateTime today, + int gracePeriodDays, CancellationToken ct) { var endDate = company.SubscriptionEndDate!.Value.Date; - var gracePeriodDays = AppConstants.SubscriptionConstants.GracePeriodDays; var expiredDate = endDate.AddDays(gracePeriodDays); // ── Status transitions ──────────────────────────────────────────── @@ -167,6 +175,7 @@ public class SubscriptionExpiryBackgroundService : BackgroundService await SendEmailIfNotSentAsync(db, emailService, company, today, NotificationType.SubscriptionExpiryReminder, daysBeforeExpiry: 0, + gracePeriodDays, ct); // Notify platform admin @@ -184,6 +193,7 @@ public class SubscriptionExpiryBackgroundService : BackgroundService await SendEmailIfNotSentAsync(db, emailService, company, today, NotificationType.SubscriptionExpiryReminder, daysBeforeExpiry: daysUntilExpiry, + gracePeriodDays, ct); } } @@ -203,6 +213,7 @@ public class SubscriptionExpiryBackgroundService : BackgroundService DateTime today, NotificationType notificationType, int daysBeforeExpiry, + int gracePeriodDays, CancellationToken ct) { if (string.IsNullOrEmpty(company.PrimaryContactEmail)) return; @@ -223,7 +234,7 @@ public class SubscriptionExpiryBackgroundService : BackgroundService if (alreadySent) return; - var (subject, plain, html) = BuildEmail(company, daysBeforeExpiry); + var (subject, plain, html) = BuildEmail(company, daysBeforeExpiry, gracePeriodDays); var (success, error) = await emailService.SendEmailAsync( company.PrimaryContactEmail, @@ -255,14 +266,15 @@ public class SubscriptionExpiryBackgroundService : BackgroundService /// private static (string subject, string plain, string html) BuildEmail( PowderCoating.Core.Entities.Company company, - int daysBeforeExpiry) + int daysBeforeExpiry, + int gracePeriodDays) { var endDate = company.SubscriptionEndDate!.Value.Date; if (daysBeforeExpiry == 0) { // Grace period notice - var graceDays = AppConstants.SubscriptionConstants.GracePeriodDays; + var graceDays = gracePeriodDays; var expiredOn = endDate.AddDays(graceDays).ToString("MMMM d, yyyy"); var subject = $"[0d] Your Powder Coating Logix subscription has expired"; var plain = $"Hi {company.CompanyName},\n\n" + diff --git a/src/PowderCoating.Web/Middleware/SubscriptionMiddleware.cs b/src/PowderCoating.Web/Middleware/SubscriptionMiddleware.cs index f20d326..86009f0 100644 --- a/src/PowderCoating.Web/Middleware/SubscriptionMiddleware.cs +++ b/src/PowderCoating.Web/Middleware/SubscriptionMiddleware.cs @@ -1,3 +1,5 @@ +using PowderCoating.Application.Interfaces; +using PowderCoating.Core.Entities; using PowderCoating.Core.Interfaces; using PowderCoating.Shared.Constants; @@ -152,7 +154,16 @@ public class SubscriptionMiddleware var daysUntil = subscriptionService.DaysUntilExpiry(company); if (daysUntil != null) { - if (daysUntil < -AppConstants.SubscriptionConstants.GracePeriodDays) + var platformSettings = context.RequestServices.GetRequiredService(); + var isTrial = string.IsNullOrEmpty(company.StripeSubscriptionId); + var graceDays = 0; + if (!isTrial || await platformSettings.GetBoolAsync(PlatformSettingKeys.GracePeriodAppliesToTrials)) + { + graceDays = await platformSettings.GetIntAsync( + PlatformSettingKeys.GracePeriodDays, + AppConstants.SubscriptionConstants.GracePeriodDays); + } + if (daysUntil < -graceDays) { // Past grace period — hard lockout context.Response.Redirect("/Billing/Expired"); diff --git a/src/PowderCoating.Web/Properties/PublishProfiles/linuxpcl - Zip Deploy.pubxml b/src/PowderCoating.Web/Properties/PublishProfiles/linuxpcl - Zip Deploy.pubxml index 0f636da..2426c9c 100644 --- a/src/PowderCoating.Web/Properties/PublishProfiles/linuxpcl - Zip Deploy.pubxml +++ b/src/PowderCoating.Web/Properties/PublishProfiles/linuxpcl - Zip Deploy.pubxml @@ -17,7 +17,7 @@ by editing this MSBuild file. In order to learn more about this please visit htt Any CPU f6a7b8c9-d0e1-4f5a-3b4c-5d6e7f8a9b0c https://linuxpcl-cvatbmbch9cchmbe.scm.centralus-01.azurewebsites.net/ - + $linuxpcl <_SavePWD>false \ No newline at end of file diff --git a/src/PowderCoating.Web/Properties/launchSettings.json b/src/PowderCoating.Web/Properties/launchSettings.json index 8a9d233..fb1fd88 100644 --- a/src/PowderCoating.Web/Properties/launchSettings.json +++ b/src/PowderCoating.Web/Properties/launchSettings.json @@ -6,7 +6,7 @@ "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, - "applicationUrl": "https://localhost:58461;http://localhost:58462" + "applicationUrl": "https://localhost:58461;http://localhost:58462;http://0.0.0.0:58462" } } } \ No newline at end of file