Fix time entry workers, powder usage logging, inventory edit, and mojibake

- JobTimeEntry: migrate to UserId/UserDisplayName; make ShopWorkerId nullable
  (migration MigrateTimeEntriesToUserId)
- Log Time modal: populate worker dropdown from Identity users instead of
  ShopWorkers; fix ShopMobile view same issue
- Inventory Ledger: scan-based JobUsage transactions now appear in
  Powder Usage By Job tab (synthesized from InventoryTransaction)
- Inventory Ledger: add Edit button for JobUsage transactions; new
  GetUsageForEdit + EditUsageTransaction endpoints; inventory-ledger.js
- InventoryTransactionRepository: include Job.Customer for ledger queries
- InventoryAiLookupService: handle JSON-LD @graph wrapper (Columbia
  Coatings / WooCommerce+Yoast); add HTML price snippet fallback
- Fix mojibake in 9 views: â†' → →, âœ" → ✓, âš  → ⚠

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-05 21:05:37 -04:00
parent 7fe8bc81c6
commit 03d3f57f7b
20 changed files with 29104 additions and 64 deletions
@@ -0,0 +1,73 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace PowderCoating.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class AddGracePeriodDaysSetting : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql("""
IF NOT EXISTS (SELECT 1 FROM [PlatformSettings] WHERE [Key] = N'GracePeriodDays')
BEGIN
INSERT INTO [PlatformSettings] ([Key], [Value], [Label], [Description], [GroupName])
VALUES (N'GracePeriodDays', N'14', N'Grace Period (days)',
N'Number of days a company can continue to log in after their subscription expires before being fully locked out.',
N'Subscriptions');
END
""");
migrationBuilder.UpdateData(
table: "PricingTiers",
keyColumn: "Id",
keyValue: 1,
column: "CreatedAt",
value: new DateTime(2026, 5, 5, 16, 17, 52, 780, DateTimeKind.Utc).AddTicks(8281));
migrationBuilder.UpdateData(
table: "PricingTiers",
keyColumn: "Id",
keyValue: 2,
column: "CreatedAt",
value: new DateTime(2026, 5, 5, 16, 17, 52, 780, DateTimeKind.Utc).AddTicks(8287));
migrationBuilder.UpdateData(
table: "PricingTiers",
keyColumn: "Id",
keyValue: 3,
column: "CreatedAt",
value: new DateTime(2026, 5, 5, 16, 17, 52, 780, DateTimeKind.Utc).AddTicks(8289));
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql("DELETE FROM [PlatformSettings] WHERE [Key] = N'GracePeriodDays'");
migrationBuilder.UpdateData(
table: "PricingTiers",
keyColumn: "Id",
keyValue: 1,
column: "CreatedAt",
value: new DateTime(2026, 5, 3, 20, 30, 44, 955, DateTimeKind.Utc).AddTicks(5184));
migrationBuilder.UpdateData(
table: "PricingTiers",
keyColumn: "Id",
keyValue: 2,
column: "CreatedAt",
value: new DateTime(2026, 5, 3, 20, 30, 44, 955, DateTimeKind.Utc).AddTicks(5189));
migrationBuilder.UpdateData(
table: "PricingTiers",
keyColumn: "Id",
keyValue: 3,
column: "CreatedAt",
value: new DateTime(2026, 5, 3, 20, 30, 44, 955, DateTimeKind.Utc).AddTicks(5191));
}
}
}
@@ -0,0 +1,73 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace PowderCoating.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class AddGracePeriodAppliesToTrials : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql("""
IF NOT EXISTS (SELECT 1 FROM [PlatformSettings] WHERE [Key] = N'GracePeriodAppliesToTrials')
BEGIN
INSERT INTO [PlatformSettings] ([Key], [Value], [Label], [Description], [GroupName])
VALUES (N'GracePeriodAppliesToTrials', N'false', N'Grace Period Applies to Trials',
N'When false (default), trial accounts (no Stripe subscription) are locked out immediately when their trial expires no grace period. Enable to give trial accounts the same grace period as paid accounts.',
N'Subscriptions');
END
""");
migrationBuilder.UpdateData(
table: "PricingTiers",
keyColumn: "Id",
keyValue: 1,
column: "CreatedAt",
value: new DateTime(2026, 5, 5, 19, 13, 29, 537, DateTimeKind.Utc).AddTicks(4011));
migrationBuilder.UpdateData(
table: "PricingTiers",
keyColumn: "Id",
keyValue: 2,
column: "CreatedAt",
value: new DateTime(2026, 5, 5, 19, 13, 29, 537, DateTimeKind.Utc).AddTicks(4019));
migrationBuilder.UpdateData(
table: "PricingTiers",
keyColumn: "Id",
keyValue: 3,
column: "CreatedAt",
value: new DateTime(2026, 5, 5, 19, 13, 29, 537, DateTimeKind.Utc).AddTicks(4021));
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql("DELETE FROM [PlatformSettings] WHERE [Key] = N'GracePeriodAppliesToTrials'");
migrationBuilder.UpdateData(
table: "PricingTiers",
keyColumn: "Id",
keyValue: 1,
column: "CreatedAt",
value: new DateTime(2026, 5, 5, 16, 17, 52, 780, DateTimeKind.Utc).AddTicks(8281));
migrationBuilder.UpdateData(
table: "PricingTiers",
keyColumn: "Id",
keyValue: 2,
column: "CreatedAt",
value: new DateTime(2026, 5, 5, 16, 17, 52, 780, DateTimeKind.Utc).AddTicks(8287));
migrationBuilder.UpdateData(
table: "PricingTiers",
keyColumn: "Id",
keyValue: 3,
column: "CreatedAt",
value: new DateTime(2026, 5, 5, 16, 17, 52, 780, DateTimeKind.Utc).AddTicks(8289));
}
}
}
@@ -0,0 +1,122 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace PowderCoating.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class MigrateTimeEntriesToUserId : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_JobTimeEntries_ShopWorkers_ShopWorkerId",
table: "JobTimeEntries");
migrationBuilder.AlterColumn<int>(
name: "ShopWorkerId",
table: "JobTimeEntries",
type: "int",
nullable: true,
oldClrType: typeof(int),
oldType: "int");
migrationBuilder.AddColumn<string>(
name: "UserDisplayName",
table: "JobTimeEntries",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "UserId",
table: "JobTimeEntries",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.UpdateData(
table: "PricingTiers",
keyColumn: "Id",
keyValue: 1,
column: "CreatedAt",
value: new DateTime(2026, 5, 5, 23, 10, 14, 763, DateTimeKind.Utc).AddTicks(8603));
migrationBuilder.UpdateData(
table: "PricingTiers",
keyColumn: "Id",
keyValue: 2,
column: "CreatedAt",
value: new DateTime(2026, 5, 5, 23, 10, 14, 763, DateTimeKind.Utc).AddTicks(8610));
migrationBuilder.UpdateData(
table: "PricingTiers",
keyColumn: "Id",
keyValue: 3,
column: "CreatedAt",
value: new DateTime(2026, 5, 5, 23, 10, 14, 763, DateTimeKind.Utc).AddTicks(8612));
migrationBuilder.AddForeignKey(
name: "FK_JobTimeEntries_ShopWorkers_ShopWorkerId",
table: "JobTimeEntries",
column: "ShopWorkerId",
principalTable: "ShopWorkers",
principalColumn: "Id");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_JobTimeEntries_ShopWorkers_ShopWorkerId",
table: "JobTimeEntries");
migrationBuilder.DropColumn(
name: "UserDisplayName",
table: "JobTimeEntries");
migrationBuilder.DropColumn(
name: "UserId",
table: "JobTimeEntries");
migrationBuilder.AlterColumn<int>(
name: "ShopWorkerId",
table: "JobTimeEntries",
type: "int",
nullable: false,
defaultValue: 0,
oldClrType: typeof(int),
oldType: "int",
oldNullable: true);
migrationBuilder.UpdateData(
table: "PricingTiers",
keyColumn: "Id",
keyValue: 1,
column: "CreatedAt",
value: new DateTime(2026, 5, 5, 19, 13, 29, 537, DateTimeKind.Utc).AddTicks(4011));
migrationBuilder.UpdateData(
table: "PricingTiers",
keyColumn: "Id",
keyValue: 2,
column: "CreatedAt",
value: new DateTime(2026, 5, 5, 19, 13, 29, 537, DateTimeKind.Utc).AddTicks(4019));
migrationBuilder.UpdateData(
table: "PricingTiers",
keyColumn: "Id",
keyValue: 3,
column: "CreatedAt",
value: new DateTime(2026, 5, 5, 19, 13, 29, 537, DateTimeKind.Utc).AddTicks(4021));
migrationBuilder.AddForeignKey(
name: "FK_JobTimeEntries_ShopWorkers_ShopWorkerId",
table: "JobTimeEntries",
column: "ShopWorkerId",
principalTable: "ShopWorkers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
}
}