From 6190650c70e1c862b0e8960fc9e8a82c06bd568b Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 23 Jun 2026 13:09:24 +0000
Subject: [PATCH 1/2] feat: Color Kitchen AL extension prototype (tables,
pages, codeunit, reports)
---
.../src/Codeunits/Cod50100.CKManagement.al | 168 +++++++++++
.../src/Enums/Enum50100.CKMixStatus.al | 18 ++
.../src/Enums/Enum50101.CKMixLineStatus.al | 14 +
.../PagExt50100.ItemCardCKExt.al | 24 ++
.../PagExt50101.SFElectronicTicketCKExt.al | 39 +++
.../src/Pages/Pag50100.CKRecipePage.al | 79 +++++
Prototyping/src/Pages/Pag50101.CKPage.al | 180 ++++++++++++
.../src/Pages/Pag50102.CKMixingCard.al | 272 ++++++++++++++++++
Prototyping/src/Pages/Pag50103.CKMixLines.al | 100 +++++++
.../src/Reports/Rep50100.CKComponentList.al | 72 +++++
.../src/Reports/Rep50100.CKComponentList.rdlc | 19 ++
.../src/Reports/Rep50101.CKLOTLabel.al | 44 +++
.../src/Reports/Rep50101.CKLOTLabel.rdlc | 19 ++
.../src/Tables/Tab50100.CKRecipeLine.al | 66 +++++
.../src/Tables/Tab50101.CKMixHeader.al | 107 +++++++
Prototyping/src/Tables/Tab50102.CKMixLine.al | 86 ++++++
16 files changed, 1307 insertions(+)
create mode 100644 Prototyping/src/Codeunits/Cod50100.CKManagement.al
create mode 100644 Prototyping/src/Enums/Enum50100.CKMixStatus.al
create mode 100644 Prototyping/src/Enums/Enum50101.CKMixLineStatus.al
create mode 100644 Prototyping/src/PageExtensions/PagExt50100.ItemCardCKExt.al
create mode 100644 Prototyping/src/PageExtensions/PagExt50101.SFElectronicTicketCKExt.al
create mode 100644 Prototyping/src/Pages/Pag50100.CKRecipePage.al
create mode 100644 Prototyping/src/Pages/Pag50101.CKPage.al
create mode 100644 Prototyping/src/Pages/Pag50102.CKMixingCard.al
create mode 100644 Prototyping/src/Pages/Pag50103.CKMixLines.al
create mode 100644 Prototyping/src/Reports/Rep50100.CKComponentList.al
create mode 100644 Prototyping/src/Reports/Rep50100.CKComponentList.rdlc
create mode 100644 Prototyping/src/Reports/Rep50101.CKLOTLabel.al
create mode 100644 Prototyping/src/Reports/Rep50101.CKLOTLabel.rdlc
create mode 100644 Prototyping/src/Tables/Tab50100.CKRecipeLine.al
create mode 100644 Prototyping/src/Tables/Tab50101.CKMixHeader.al
create mode 100644 Prototyping/src/Tables/Tab50102.CKMixLine.al
diff --git a/Prototyping/src/Codeunits/Cod50100.CKManagement.al b/Prototyping/src/Codeunits/Cod50100.CKManagement.al
new file mode 100644
index 0000000..08b71fa
--- /dev/null
+++ b/Prototyping/src/Codeunits/Cod50100.CKManagement.al
@@ -0,0 +1,168 @@
+codeunit 50100 "CK Management"
+{
+ ///
+ /// Generates a unique LOT number for a newly mixed ink batch.
+ /// Format: CK-YYYYMMDDHHMMSS
+ ///
+ procedure GenerateLOTNo(): Code[50]
+ var
+ DateTimeText: Text;
+ begin
+ DateTimeText := Format(CurrentDateTime(), 0, '');
+ exit(CopyStr('CK-' + DateTimeText, 1, 50));
+ end;
+
+ ///
+ /// Populates Mix Lines from the Color Kitchen Recipe for the given Mix Header.
+ /// Existing lines are replaced. Quantities are calculated from the recipe percentages
+ /// and the Quantity to Mix on the header.
+ ///
+ procedure LoadRecipeLines(MixHeaderEntryNo: Integer)
+ var
+ MixHeader: Record "CK Mix Header";
+ RecipeLine: Record "CK Recipe Line";
+ MixLine: Record "CK Mix Line";
+ LineNo: Integer;
+ begin
+ if not MixHeader.Get(MixHeaderEntryNo) then
+ exit;
+
+ // Remove any previously loaded lines so we start fresh
+ MixLine.SetRange("Mix Header Entry No.", MixHeaderEntryNo);
+ MixLine.DeleteAll(true);
+
+ RecipeLine.SetRange("Item No.", MixHeader."Item No.");
+ if not RecipeLine.FindSet() then
+ exit;
+
+ LineNo := 10000;
+ repeat
+ MixLine.Init();
+ MixLine."Mix Header Entry No." := MixHeaderEntryNo;
+ MixLine."Line No." := LineNo;
+ MixLine."Base Component Item No." := RecipeLine."Base Component Item No.";
+ MixLine."Base Component Description" := RecipeLine."Base Component Description";
+ MixLine.Percentage := RecipeLine.Percentage;
+ if MixHeader."Quantity to Mix" > 0 then
+ MixLine."Quantity Required" :=
+ Round(MixHeader."Quantity to Mix" * RecipeLine.Percentage / 100, 0.001);
+ MixLine."Quantity Used" := MixLine."Quantity Required";
+ MixLine.Status := "CK Mix Line Status"::Pending;
+ MixLine.Insert(true);
+ LineNo += 10000;
+ until RecipeLine.Next() = 0;
+ end;
+
+ ///
+ /// Posts negative item adjustments for each pending base component line,
+ /// recording the operator-supplied LOT numbers. Advances header status to In Progress.
+ ///
+ procedure PostConsumption(MixHeaderEntryNo: Integer)
+ var
+ MixHeader: Record "CK Mix Header";
+ MixLine: Record "CK Mix Line";
+ ItemJnlLine: Record "Item Journal Line";
+ ItemJnlPostLine: Codeunit "Item Jnl.-Post Line";
+ LineNo: Integer;
+ DocumentNo: Code[20];
+ begin
+ if not MixHeader.Get(MixHeaderEntryNo) then
+ exit;
+
+ DocumentNo := CopyStr('CK-' + Format(MixHeaderEntryNo), 1, 20);
+
+ MixLine.SetRange("Mix Header Entry No.", MixHeaderEntryNo);
+ MixLine.SetRange(Status, "CK Mix Line Status"::Pending);
+ if not MixLine.FindSet() then
+ exit;
+
+ LineNo := 10000;
+ repeat
+ if MixLine."Quantity Used" > 0 then begin
+ Clear(ItemJnlLine);
+ ItemJnlLine."Line No." := LineNo;
+ ItemJnlLine.Validate("Posting Date", Today());
+ ItemJnlLine.Validate("Entry Type", ItemJnlLine."Entry Type"::"Negative Adjmt.");
+ ItemJnlLine.Validate("Item No.", MixLine."Base Component Item No.");
+ ItemJnlLine.Validate(Quantity, MixLine."Quantity Used");
+ ItemJnlLine."Lot No." := MixLine."LOT No.";
+ ItemJnlLine."Document No." := DocumentNo;
+ ItemJnlLine."Source Code" := GetSourceCode();
+ ItemJnlPostLine.RunWithCheck(ItemJnlLine);
+
+ MixLine.Status := "CK Mix Line Status"::Consumed;
+ MixLine.Modify(true);
+ end;
+ LineNo += 10000;
+ until MixLine.Next() = 0;
+
+ MixHeader.Status := "CK Mix Status"::"In Progress";
+ MixHeader.Modify(true);
+ end;
+
+ ///
+ /// Generates a new LOT number if needed, then posts a positive item adjustment
+ /// to put the mixed ink into stock. Advances header status to Completed.
+ ///
+ procedure PostMixedInkToStock(MixHeaderEntryNo: Integer)
+ var
+ MixHeader: Record "CK Mix Header";
+ ItemJnlLine: Record "Item Journal Line";
+ ItemJnlPostLine: Codeunit "Item Jnl.-Post Line";
+ DocumentNo: Code[20];
+ begin
+ if not MixHeader.Get(MixHeaderEntryNo) then
+ exit;
+
+ if MixHeader."LOT No." = '' then
+ MixHeader."LOT No." := GenerateLOTNo();
+
+ if MixHeader."Quantity Mixed" = 0 then
+ MixHeader."Quantity Mixed" := MixHeader."Quantity to Mix";
+
+ DocumentNo := CopyStr('CK-' + Format(MixHeaderEntryNo), 1, 20);
+
+ Clear(ItemJnlLine);
+ ItemJnlLine."Line No." := 10000;
+ ItemJnlLine.Validate("Posting Date", Today());
+ ItemJnlLine.Validate("Entry Type", ItemJnlLine."Entry Type"::"Positive Adjmt.");
+ ItemJnlLine.Validate("Item No.", MixHeader."Item No.");
+ ItemJnlLine.Validate(Quantity, MixHeader."Quantity Mixed");
+ ItemJnlLine."Lot No." := MixHeader."LOT No.";
+ ItemJnlLine."Document No." := DocumentNo;
+ ItemJnlLine."Source Code" := GetSourceCode();
+ ItemJnlPostLine.RunWithCheck(ItemJnlLine);
+
+ MixHeader.Status := "CK Mix Status"::Completed;
+ MixHeader.Modify(true);
+ end;
+
+ ///
+ /// Returns a formatted text of all base component LOT numbers recorded on a mix.
+ /// Used for label printing and job ticket display.
+ ///
+ procedure GetBaseComponentLOTsText(MixHeaderEntryNo: Integer): Text
+ var
+ MixLine: Record "CK Mix Line";
+ LOTText: Text;
+ begin
+ MixLine.SetRange("Mix Header Entry No.", MixHeaderEntryNo);
+ MixLine.SetFilter("LOT No.", '<>%1', '');
+ if MixLine.FindSet() then
+ repeat
+ if LOTText <> '' then
+ LOTText += '; ';
+ LOTText += MixLine."Base Component Item No." + ': ' + MixLine."LOT No.";
+ until MixLine.Next() = 0;
+ exit(LOTText);
+ end;
+
+ local procedure GetSourceCode(): Code[10]
+ var
+ SourceCodeSetup: Record "Source Code Setup";
+ begin
+ if SourceCodeSetup.Get() then
+ exit(SourceCodeSetup."Item Journal");
+ exit('');
+ end;
+}
diff --git a/Prototyping/src/Enums/Enum50100.CKMixStatus.al b/Prototyping/src/Enums/Enum50100.CKMixStatus.al
new file mode 100644
index 0000000..644b680
--- /dev/null
+++ b/Prototyping/src/Enums/Enum50100.CKMixStatus.al
@@ -0,0 +1,18 @@
+enum 50100 "CK Mix Status"
+{
+ Extensible = true;
+ Caption = 'Color Kitchen Mix Status';
+
+ value(0; "Not Started")
+ {
+ Caption = 'Not Started';
+ }
+ value(1; "In Progress")
+ {
+ Caption = 'In Progress';
+ }
+ value(2; Completed)
+ {
+ Caption = 'Completed';
+ }
+}
diff --git a/Prototyping/src/Enums/Enum50101.CKMixLineStatus.al b/Prototyping/src/Enums/Enum50101.CKMixLineStatus.al
new file mode 100644
index 0000000..4305b7f
--- /dev/null
+++ b/Prototyping/src/Enums/Enum50101.CKMixLineStatus.al
@@ -0,0 +1,14 @@
+enum 50101 "CK Mix Line Status"
+{
+ Extensible = true;
+ Caption = 'Color Kitchen Mix Line Status';
+
+ value(0; Pending)
+ {
+ Caption = 'Pending';
+ }
+ value(1; Consumed)
+ {
+ Caption = 'Consumed';
+ }
+}
diff --git a/Prototyping/src/PageExtensions/PagExt50100.ItemCardCKExt.al b/Prototyping/src/PageExtensions/PagExt50100.ItemCardCKExt.al
new file mode 100644
index 0000000..cf638b4
--- /dev/null
+++ b/Prototyping/src/PageExtensions/PagExt50100.ItemCardCKExt.al
@@ -0,0 +1,24 @@
+pageextension 50100 "CK Item Card Ext" extends "Item Card"
+{
+ actions
+ {
+ addlast(Navigation)
+ {
+ action(ColorKitchenRecipe)
+ {
+ Caption = 'Color Kitchen Recipe';
+ ApplicationArea = All;
+ Image = BOMVersions;
+ ToolTip = 'Opens the Color Kitchen Recipe for this item. Define the base component inks and their percentages that make up this mixed ink.';
+
+ trigger OnAction()
+ var
+ RecipeLine: Record "CK Recipe Line";
+ begin
+ RecipeLine.SetRange("Item No.", Rec."No.");
+ Page.Run(Page::"CK Recipe Page", RecipeLine);
+ end;
+ }
+ }
+ }
+}
diff --git a/Prototyping/src/PageExtensions/PagExt50101.SFElectronicTicketCKExt.al b/Prototyping/src/PageExtensions/PagExt50101.SFElectronicTicketCKExt.al
new file mode 100644
index 0000000..cc41eae
--- /dev/null
+++ b/Prototyping/src/PageExtensions/PagExt50101.SFElectronicTicketCKExt.al
@@ -0,0 +1,39 @@
+// Extends the PrintVis Shop Floor Electronic Ticket page (6010731).
+// Adds the Color Kitchen button to the header so the mixing station operator
+// can open the Color Kitchen directly from their active job ticket.
+//
+// NOTE: The field names "Case No." and "Job No." used below reflect the typical
+// PrintVis Job Sheet structure. Verify these field names match your PrintVis version
+// and adjust if required before compiling.
+pageextension 50101 "CK SF Electronic Ticket Ext" extends "PVS SF Electronic Ticket"
+{
+ actions
+ {
+ addlast(Processing)
+ {
+ action(ColorKitchen)
+ {
+ Caption = 'Color Kitchen';
+ ApplicationArea = All;
+ Image = ResourcePlanning;
+ ToolTip = 'Opens the Color Kitchen for this job. Mix inks, scan LOT numbers and put mixed ink into stock.';
+
+ trigger OnAction()
+ var
+ MixHeader: Record "CK Mix Header";
+ CaseNo: Integer;
+ JobNo: Integer;
+ begin
+ // Retrieve the PrintVis Case and Job numbers from the current record.
+ // Adjust the field names below if they differ in your PrintVis installation.
+ CaseNo := Rec."Case No.";
+ JobNo := Rec."Job No.";
+
+ MixHeader.SetRange("PVS Case No.", CaseNo);
+ MixHeader.SetRange("PVS Job No.", JobNo);
+ Page.Run(Page::"CK Page", MixHeader);
+ end;
+ }
+ }
+ }
+}
diff --git a/Prototyping/src/Pages/Pag50100.CKRecipePage.al b/Prototyping/src/Pages/Pag50100.CKRecipePage.al
new file mode 100644
index 0000000..b84bd8f
--- /dev/null
+++ b/Prototyping/src/Pages/Pag50100.CKRecipePage.al
@@ -0,0 +1,79 @@
+page 50100 "CK Recipe Page"
+{
+ Caption = 'Color Kitchen Recipe';
+ PageType = List;
+ SourceTable = "CK Recipe Line";
+ UsageCategory = None;
+ Editable = true;
+ InsertAllowed = true;
+ DeleteAllowed = true;
+
+ layout
+ {
+ area(Content)
+ {
+ repeater(Lines)
+ {
+ field("Base Component Item No."; Rec."Base Component Item No.")
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies the item number of the base component ink.';
+ }
+ field("Base Component Description"; Rec."Base Component Description")
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies the description of the base component ink.';
+ }
+ field(Percentage; Rec.Percentage)
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies the weight percentage of this component in the mixed ink recipe. All percentages should sum to 100.';
+ }
+ }
+ }
+ }
+
+ actions
+ {
+ area(Processing)
+ {
+ action(ValidateTotal)
+ {
+ Caption = 'Check Total %';
+ ApplicationArea = All;
+ Image = CheckRecs;
+ ToolTip = 'Validates that all recipe percentages add up to 100.';
+
+ trigger OnAction()
+ var
+ RecipeLine: Record "CK Recipe Line";
+ Total: Decimal;
+ ItemFilter: Code[20];
+ begin
+ ItemFilter := CopyStr(Rec.GetFilter("Item No."), 1, 20);
+ RecipeLine.SetRange("Item No.", ItemFilter);
+ if RecipeLine.FindSet() then
+ repeat
+ Total += RecipeLine.Percentage;
+ until RecipeLine.Next() = 0;
+
+ if Abs(Total - 100) < 0.01 then
+ Message('Recipe total is 100%%. OK.')
+ else
+ Message('Recipe total is %1%%. Components should add up to 100%%.', Total);
+ end;
+ }
+ }
+ }
+
+ trigger OnNewRecord(BelowxRec: Boolean)
+ var
+ ItemFilter: Code[20];
+ begin
+ ItemFilter := CopyStr(Rec.GetFilter("Item No."), 1, 20);
+ if ItemFilter <> '' then begin
+ Rec."Item No." := ItemFilter;
+ Rec."Line No." := Rec.GetNextLineNo(ItemFilter);
+ end;
+ end;
+}
diff --git a/Prototyping/src/Pages/Pag50101.CKPage.al b/Prototyping/src/Pages/Pag50101.CKPage.al
new file mode 100644
index 0000000..85a4310
--- /dev/null
+++ b/Prototyping/src/Pages/Pag50101.CKPage.al
@@ -0,0 +1,180 @@
+page 50101 "CK Page"
+{
+ Caption = 'Color Kitchen';
+ PageType = List;
+ SourceTable = "CK Mix Header";
+ UsageCategory = None;
+ CardPageId = "CK Mixing Card";
+
+ layout
+ {
+ area(Content)
+ {
+ repeater(MixJobs)
+ {
+ field("Item No."; Rec."Item No.")
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies the mixed ink item to be produced.';
+ }
+ field("Item Description"; Rec."Item Description")
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies the description of the mixed ink.';
+ }
+ field("Quantity to Mix"; Rec."Quantity to Mix")
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies the planned quantity of mixed ink needed.';
+ }
+ field("Quantity Mixed"; Rec."Quantity Mixed")
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies the actual quantity of mixed ink that has been produced.';
+ }
+ field("Quantity in Stock"; Rec."Quantity in Stock")
+ {
+ ApplicationArea = All;
+ ToolTip = 'Shows the current inventory level of this mixed ink item.';
+ }
+ field("Unit of Measure Code"; Rec."Unit of Measure Code")
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies the unit of measure.';
+ }
+ field("Eco Label Codes"; Rec."Eco Label Codes")
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies the eco/food-safe certifications required for this ink (e.g. FSC, food-safe).';
+ }
+ field(Status; Rec.Status)
+ {
+ ApplicationArea = All;
+ StyleExpr = StatusStyle;
+ ToolTip = 'Specifies the current mixing status.';
+ }
+ field("LOT No."; Rec."LOT No.")
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies the LOT number assigned to the produced mixed ink.';
+ }
+ field("PVS Case No."; Rec."PVS Case No.")
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies the PrintVis Case number this mixing job belongs to.';
+ Visible = false;
+ }
+ field("Created At"; Rec."Created At")
+ {
+ ApplicationArea = All;
+ Visible = false;
+ ToolTip = 'Specifies when this mixing job was created.';
+ }
+ }
+ }
+ }
+
+ actions
+ {
+ area(Processing)
+ {
+ action(OpenMixingCard)
+ {
+ Caption = 'Open Mixing Card';
+ ApplicationArea = All;
+ Image = Navigate;
+ ShortCutKey = 'Return';
+ ToolTip = 'Opens the mixing card for the selected ink to scan LOT numbers, adjust quantities and post consumption.';
+
+ trigger OnAction()
+ begin
+ Page.Run(Page::"CK Mixing Card", Rec);
+ end;
+ }
+ action(StartAllMixing)
+ {
+ Caption = 'Start Mixing (Selected)';
+ ApplicationArea = All;
+ Image = Start;
+ ToolTip = 'Sets the selected mixing jobs to In Progress and loads recipe lines if not yet done.';
+
+ trigger OnAction()
+ var
+ SelectedHeader: Record "CK Mix Header";
+ CKMgt: Codeunit "CK Management";
+ MixLine: Record "CK Mix Line";
+ Counter: Integer;
+ begin
+ CurrPage.SetSelectionFilter(SelectedHeader);
+ if not SelectedHeader.FindSet(true) then
+ exit;
+ repeat
+ if SelectedHeader.Status = "CK Mix Status"::"Not Started" then begin
+ MixLine.SetRange("Mix Header Entry No.", SelectedHeader."Entry No.");
+ if MixLine.IsEmpty() then
+ CKMgt.LoadRecipeLines(SelectedHeader."Entry No.");
+ SelectedHeader.Validate(Status, "CK Mix Status"::"In Progress");
+ SelectedHeader.Modify(true);
+ Counter += 1;
+ end;
+ until SelectedHeader.Next() = 0;
+ if Counter > 0 then
+ Message('%1 mixing job(s) started.', Counter);
+ end;
+ }
+ }
+ area(Reporting)
+ {
+ action(PrintComponentList)
+ {
+ Caption = 'Print Component List';
+ ApplicationArea = All;
+ Image = Print;
+ ToolTip = 'Prints the full list of base components required for the selected mixed inks, including LOT numbers and shelf locations.';
+
+ trigger OnAction()
+ var
+ SelectedHeader: Record "CK Mix Header";
+ begin
+ CurrPage.SetSelectionFilter(SelectedHeader);
+ Report.RunModal(Report::"CK Component List", true, false, SelectedHeader);
+ end;
+ }
+ action(PrintLOTLabels)
+ {
+ Caption = 'Print LOT Labels';
+ ApplicationArea = All;
+ Image = BarCode;
+ ToolTip = 'Prints LOT labels for the selected completed mixed inks.';
+
+ trigger OnAction()
+ var
+ SelectedHeader: Record "CK Mix Header";
+ begin
+ CurrPage.SetSelectionFilter(SelectedHeader);
+ Report.RunModal(Report::"CK LOT Label", true, false, SelectedHeader);
+ end;
+ }
+ }
+ }
+
+ trigger OnAfterGetRecord()
+ begin
+ StatusStyle := GetStatusStyle();
+ end;
+
+ var
+ StatusStyle: Text;
+
+ local procedure GetStatusStyle(): Text
+ begin
+ case Rec.Status of
+ "CK Mix Status"::Completed:
+ exit('Favorable');
+ "CK Mix Status"::"In Progress":
+ exit('Ambiguous');
+ else
+ exit('None');
+ end;
+ end;
+}
diff --git a/Prototyping/src/Pages/Pag50102.CKMixingCard.al b/Prototyping/src/Pages/Pag50102.CKMixingCard.al
new file mode 100644
index 0000000..291cf9a
--- /dev/null
+++ b/Prototyping/src/Pages/Pag50102.CKMixingCard.al
@@ -0,0 +1,272 @@
+page 50102 "CK Mixing Card"
+{
+ Caption = 'Color Kitchen – Mixing';
+ PageType = Card;
+ SourceTable = "CK Mix Header";
+ UsageCategory = None;
+
+ layout
+ {
+ area(Content)
+ {
+ group(General)
+ {
+ Caption = 'General';
+
+ field("Entry No."; Rec."Entry No.")
+ {
+ ApplicationArea = All;
+ Editable = false;
+ ToolTip = 'Specifies the unique identifier of this mixing job.';
+ }
+ field("Item No."; Rec."Item No.")
+ {
+ ApplicationArea = All;
+ Importance = Promoted;
+ ToolTip = 'Specifies the mixed ink item to be produced.';
+ Editable = IsNotCompleted;
+ }
+ field("Item Description"; Rec."Item Description")
+ {
+ ApplicationArea = All;
+ Editable = false;
+ ToolTip = 'Specifies the description of the mixed ink.';
+ }
+ field("Unit of Measure Code"; Rec."Unit of Measure Code")
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies the unit of measure for the mixed ink.';
+ Editable = IsNotCompleted;
+ }
+ field(Status; Rec.Status)
+ {
+ ApplicationArea = All;
+ Editable = false;
+ Importance = Promoted;
+ StyleExpr = HeaderStatusStyle;
+ ToolTip = 'Specifies the current status of this mixing job.';
+ }
+ }
+ group(Quantities)
+ {
+ Caption = 'Quantities';
+
+ field("Quantity to Mix"; Rec."Quantity to Mix")
+ {
+ ApplicationArea = All;
+ Importance = Promoted;
+ ToolTip = 'Specifies the planned quantity of mixed ink to produce.';
+ Editable = IsNotCompleted;
+ }
+ field("Quantity Mixed"; Rec."Quantity Mixed")
+ {
+ ApplicationArea = All;
+ Importance = Promoted;
+ ToolTip = 'Specifies the actual quantity of mixed ink that was produced. Can differ from the planned quantity.';
+ Editable = IsNotCompleted;
+ }
+ field("Quantity in Stock"; Rec."Quantity in Stock")
+ {
+ ApplicationArea = All;
+ Editable = false;
+ ToolTip = 'Shows the current inventory quantity of this mixed ink item.';
+ }
+ }
+ group(Traceability)
+ {
+ Caption = 'Traceability & Labels';
+
+ field("LOT No."; Rec."LOT No.")
+ {
+ ApplicationArea = All;
+ Importance = Promoted;
+ ToolTip = 'Specifies the LOT number assigned to the newly produced mixed ink batch.';
+ Editable = IsNotCompleted;
+ }
+ field("Eco Label Codes"; Rec."Eco Label Codes")
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies the eco/food-safe label certifications that apply to this mixed ink (e.g. FSC, food-safe).';
+ Editable = IsNotCompleted;
+ }
+ field(Notes; Rec.Notes)
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies any additional notes for this mixing job.';
+ Editable = IsNotCompleted;
+ }
+ field("Created At"; Rec."Created At")
+ {
+ ApplicationArea = All;
+ Editable = false;
+ ToolTip = 'Specifies when this mixing job was created.';
+ }
+ }
+ part(BaseComponentLines; "CK Mix Lines")
+ {
+ ApplicationArea = All;
+ Caption = 'Base Components';
+ SubPageLink = "Mix Header Entry No." = field("Entry No.");
+ UpdatePropagation = Both;
+ }
+ }
+ }
+
+ actions
+ {
+ area(Processing)
+ {
+ action(LoadFromRecipe)
+ {
+ Caption = 'Load from Recipe';
+ ApplicationArea = All;
+ Image = BOMVersions;
+ Enabled = IsNotCompleted;
+ ToolTip = 'Populates the base component lines from the Color Kitchen Recipe defined for this mixed ink item. Existing lines are replaced.';
+
+ trigger OnAction()
+ var
+ CKMgt: Codeunit "CK Management";
+ begin
+ if not Confirm('Load recipe lines for %1? Existing lines will be replaced.', true, Rec."Item No.") then
+ exit;
+ CKMgt.LoadRecipeLines(Rec."Entry No.");
+ CurrPage.BaseComponentLines.Page.Update(false);
+ end;
+ }
+ action(ConsumeComponents)
+ {
+ Caption = 'Consume Base Components';
+ ApplicationArea = All;
+ Image = PostingEntries;
+ Enabled = IsInProgress;
+ ToolTip = 'Posts negative inventory adjustments for all pending base component lines, recording their LOT numbers.';
+
+ trigger OnAction()
+ var
+ CKMgt: Codeunit "CK Management";
+ begin
+ if not Confirm('Consume all pending base components now? This will post inventory adjustments.', true) then
+ exit;
+ CKMgt.PostConsumption(Rec."Entry No.");
+ Rec.Get(Rec."Entry No.");
+ CurrPage.Update(false);
+ end;
+ }
+ action(CompleteMixing)
+ {
+ Caption = 'Complete Mixing';
+ ApplicationArea = All;
+ Image = Approve;
+ Enabled = IsNotCompleted;
+ ToolTip = 'Posts the finished mixed ink quantity to inventory and marks this mixing job as completed.';
+
+ trigger OnAction()
+ var
+ CKMgt: Codeunit "CK Management";
+ begin
+ if not Confirm('Complete mixing and put %1 %2 of %3 into stock?', true,
+ Rec."Quantity Mixed", Rec."Unit of Measure Code", Rec."Item No.")
+ then
+ exit;
+ CKMgt.PostMixedInkToStock(Rec."Entry No.");
+ Rec.Get(Rec."Entry No.");
+ CurrPage.Update(false);
+ end;
+ }
+ action(StartMixing)
+ {
+ Caption = 'Start Mixing';
+ ApplicationArea = All;
+ Image = Start;
+ Enabled = IsNotStarted;
+ ToolTip = 'Marks this mixing job as In Progress and loads the recipe lines if they have not already been loaded.';
+
+ trigger OnAction()
+ var
+ CKMgt: Codeunit "CK Management";
+ MixLine: Record "CK Mix Line";
+ begin
+ MixLine.SetRange("Mix Header Entry No.", Rec."Entry No.");
+ if MixLine.IsEmpty() then
+ CKMgt.LoadRecipeLines(Rec."Entry No.");
+ Rec.Validate(Status, "CK Mix Status"::"In Progress");
+ Rec.Modify(true);
+ CurrPage.Update(false);
+ end;
+ }
+ }
+ area(Reporting)
+ {
+ action(PrintComponentList)
+ {
+ Caption = 'Print Component List';
+ ApplicationArea = All;
+ Image = Print;
+ ToolTip = 'Prints the list of base components needed for this mixing job, including LOT numbers and shelf locations.';
+
+ trigger OnAction()
+ var
+ MixHeader: Record "CK Mix Header";
+ begin
+ MixHeader.SetRange("Entry No.", Rec."Entry No.");
+ Report.RunModal(Report::"CK Component List", true, false, MixHeader);
+ end;
+ }
+ action(PrintLOTLabel)
+ {
+ Caption = 'Print LOT Label';
+ ApplicationArea = All;
+ Image = BarCode;
+ Enabled = HasLOTNo;
+ ToolTip = 'Prints the LOT label for the newly produced mixed ink, including all base component LOT numbers for traceability.';
+
+ trigger OnAction()
+ var
+ MixHeader: Record "CK Mix Header";
+ begin
+ MixHeader.SetRange("Entry No.", Rec."Entry No.");
+ Report.RunModal(Report::"CK LOT Label", true, false, MixHeader);
+ end;
+ }
+ }
+ }
+
+ trigger OnAfterGetRecord()
+ begin
+ SetPageVariables();
+ end;
+
+ trigger OnAfterGetCurrRecord()
+ begin
+ SetPageVariables();
+ end;
+
+ var
+ HeaderStatusStyle: Text;
+ IsNotStarted: Boolean;
+ IsInProgress: Boolean;
+ IsNotCompleted: Boolean;
+ HasLOTNo: Boolean;
+
+ local procedure SetPageVariables()
+ begin
+ IsNotStarted := Rec.Status = "CK Mix Status"::"Not Started";
+ IsInProgress := Rec.Status = "CK Mix Status"::"In Progress";
+ IsNotCompleted := Rec.Status <> "CK Mix Status"::Completed;
+ HasLOTNo := Rec."LOT No." <> '';
+ HeaderStatusStyle := GetHeaderStatusStyle();
+ end;
+
+ local procedure GetHeaderStatusStyle(): Text
+ begin
+ case Rec.Status of
+ "CK Mix Status"::Completed:
+ exit('Favorable');
+ "CK Mix Status"::"In Progress":
+ exit('Ambiguous');
+ else
+ exit('None');
+ end;
+ end;
+}
diff --git a/Prototyping/src/Pages/Pag50103.CKMixLines.al b/Prototyping/src/Pages/Pag50103.CKMixLines.al
new file mode 100644
index 0000000..4541491
--- /dev/null
+++ b/Prototyping/src/Pages/Pag50103.CKMixLines.al
@@ -0,0 +1,100 @@
+page 50103 "CK Mix Lines"
+{
+ Caption = 'Base Components';
+ PageType = ListPart;
+ SourceTable = "CK Mix Line";
+ UsageCategory = None;
+ Editable = true;
+ InsertAllowed = true;
+ DeleteAllowed = true;
+
+ layout
+ {
+ area(Content)
+ {
+ repeater(Lines)
+ {
+ field("Base Component Item No."; Rec."Base Component Item No.")
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies the item number of the base component ink.';
+ Editable = IsEditable;
+ }
+ field("Base Component Description"; Rec."Base Component Description")
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies the description of the base component ink.';
+ Editable = false;
+ }
+ field(Percentage; Rec.Percentage)
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies the percentage this component contributes to the mixed ink.';
+ }
+ field("Quantity Required"; Rec."Quantity Required")
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies the calculated quantity of this component needed for the mix.';
+ }
+ field("Quantity Used"; Rec."Quantity Used")
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies the actual quantity consumed. Can be adjusted by the operator.';
+ Editable = IsEditable;
+ }
+ field("LOT No."; Rec."LOT No.")
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies the LOT number scanned or entered for this base component. This LOT is stored on the mixed ink for traceability.';
+ Editable = IsEditable;
+ }
+ field("Shelf No."; Rec."Shelf No.")
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies the shelf or bin location of this base component to help the operator locate it.';
+ Editable = IsEditable;
+ }
+ field(Status; Rec.Status)
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies whether this component has been consumed.';
+ Editable = false;
+ StyleExpr = LineStatusStyle;
+ }
+ }
+ }
+ }
+
+ trigger OnAfterGetRecord()
+ begin
+ LineStatusStyle := GetLineStatusStyle();
+ IsEditable := Rec.Status = "CK Mix Line Status"::Pending;
+ end;
+
+ trigger OnNewRecord(BelowxRec: Boolean)
+ var
+ HeaderNo: Integer;
+ HeaderFilter: Text;
+ begin
+ HeaderFilter := Rec.GetFilter("Mix Header Entry No.");
+ if (HeaderFilter <> '') and Evaluate(HeaderNo, HeaderFilter) then begin
+ Rec."Mix Header Entry No." := HeaderNo;
+ Rec."Line No." := Rec.GetNextLineNo(HeaderNo);
+ end;
+ IsEditable := true;
+ end;
+
+ var
+ LineStatusStyle: Text;
+ IsEditable: Boolean;
+
+ local procedure GetLineStatusStyle(): Text
+ begin
+ case Rec.Status of
+ "CK Mix Line Status"::Consumed:
+ exit('Favorable');
+ else
+ exit('None');
+ end;
+ end;
+}
diff --git a/Prototyping/src/Reports/Rep50100.CKComponentList.al b/Prototyping/src/Reports/Rep50100.CKComponentList.al
new file mode 100644
index 0000000..05cb131
--- /dev/null
+++ b/Prototyping/src/Reports/Rep50100.CKComponentList.al
@@ -0,0 +1,72 @@
+report 50100 "CK Component List"
+{
+ Caption = 'Color Kitchen – Component List';
+ UsageCategory = None;
+ ApplicationArea = All;
+ DefaultLayout = RDLC;
+ RDLCLayout = 'src/Reports/Rep50100.CKComponentList.rdlc';
+
+ dataset
+ {
+ dataitem(MixHeader; "CK Mix Header")
+ {
+ RequestFilterFields = "Entry No.", "PVS Case No.", "PVS Job No.";
+ PrintOnlyIfDetail = true;
+
+ column(CaseNo; MixHeader."PVS Case No.") { }
+ column(JobNo; MixHeader."PVS Job No.") { }
+ column(MixedInkItemNo; MixHeader."Item No.") { }
+ column(MixedInkDescription; MixHeader."Item Description") { }
+ column(QuantityToMix; MixHeader."Quantity to Mix") { }
+ column(UnitOfMeasure; MixHeader."Unit of Measure Code") { }
+ column(EcoLabels; MixHeader."Eco Label Codes") { }
+ column(MixStatus; Format(MixHeader.Status)) { }
+ column(PrintDate; Format(Today())) { }
+
+ dataitem(MixLine; "CK Mix Line")
+ {
+ DataItemLink = "Mix Header Entry No." = field("Entry No.");
+ DataItemTableView = sorting("Mix Header Entry No.", "Line No.");
+
+ column(LineNo; MixLine."Line No.") { }
+ column(ComponentItemNo; MixLine."Base Component Item No.") { }
+ column(ComponentDescription; MixLine."Base Component Description") { }
+ column(Percentage; MixLine.Percentage) { }
+ column(QuantityRequired; MixLine."Quantity Required") { }
+ column(QuantityUsed; MixLine."Quantity Used") { }
+ column(ComponentLOTNo; MixLine."LOT No.") { }
+ column(ShelfNo; MixLine."Shelf No.") { }
+ column(LineStatus; Format(MixLine.Status)) { }
+
+ trigger OnPreDataItem()
+ begin
+ if not ShowConsumed then
+ MixLine.SetRange(Status, "CK Mix Line Status"::Pending);
+ end;
+ }
+ }
+ }
+
+ requestpage
+ {
+ layout
+ {
+ area(Content)
+ {
+ group(Options)
+ {
+ Caption = 'Options';
+ field(ShowConsumedLines; ShowConsumed)
+ {
+ ApplicationArea = All;
+ Caption = 'Include Already Consumed Components';
+ ToolTip = 'Check to include base component lines that have already been consumed.';
+ }
+ }
+ }
+ }
+ }
+
+ var
+ ShowConsumed: Boolean;
+}
diff --git a/Prototyping/src/Reports/Rep50100.CKComponentList.rdlc b/Prototyping/src/Reports/Rep50100.CKComponentList.rdlc
new file mode 100644
index 0000000..f6b3d45
--- /dev/null
+++ b/Prototyping/src/Reports/Rep50100.CKComponentList.rdlc
@@ -0,0 +1,19 @@
+
+
+ 0
+
+
+ 1cm
+
+ 21cm
+
+ 29.7cm
+ 21cm
+ 2cm
+ 2cm
+ 2cm
+ 2cm
+
+ Cm
+
diff --git a/Prototyping/src/Reports/Rep50101.CKLOTLabel.al b/Prototyping/src/Reports/Rep50101.CKLOTLabel.al
new file mode 100644
index 0000000..9e52ee8
--- /dev/null
+++ b/Prototyping/src/Reports/Rep50101.CKLOTLabel.al
@@ -0,0 +1,44 @@
+report 50101 "CK LOT Label"
+{
+ Caption = 'Color Kitchen – LOT Label';
+ UsageCategory = None;
+ ApplicationArea = All;
+ DefaultLayout = RDLC;
+ RDLCLayout = 'src/Reports/Rep50101.CKLOTLabel.rdlc';
+
+ dataset
+ {
+ dataitem(MixHeader; "CK Mix Header")
+ {
+ RequestFilterFields = "Entry No.", "LOT No.";
+ DataItemTableView = where(Status = const(Completed));
+
+ column(MixedInkItemNo; MixHeader."Item No.") { }
+ column(MixedInkDescription; MixHeader."Item Description") { }
+ column(MixedInkLOTNo; MixHeader."LOT No.") { }
+ column(QuantityMixed; MixHeader."Quantity Mixed") { }
+ column(UnitOfMeasure; MixHeader."Unit of Measure Code") { }
+ column(EcoLabels; MixHeader."Eco Label Codes") { }
+ column(CaseNo; MixHeader."PVS Case No.") { }
+ column(JobNo; MixHeader."PVS Job No.") { }
+ column(CreatedAt; Format(MixHeader."Created At")) { }
+ column(PrintDate; Format(Today())) { }
+ column(BaseComponentLOTs; BaseComponentLOTsText) { }
+
+ trigger OnAfterGetRecord()
+ begin
+ BaseComponentLOTsText := GetBaseComponentLOTs();
+ end;
+ }
+ }
+
+ var
+ BaseComponentLOTsText: Text;
+
+ local procedure GetBaseComponentLOTs(): Text
+ var
+ CKMgt: Codeunit "CK Management";
+ begin
+ exit(CKMgt.GetBaseComponentLOTsText(MixHeader."Entry No."));
+ end;
+}
diff --git a/Prototyping/src/Reports/Rep50101.CKLOTLabel.rdlc b/Prototyping/src/Reports/Rep50101.CKLOTLabel.rdlc
new file mode 100644
index 0000000..f6b3d45
--- /dev/null
+++ b/Prototyping/src/Reports/Rep50101.CKLOTLabel.rdlc
@@ -0,0 +1,19 @@
+
+
+ 0
+
+
+ 1cm
+
+ 21cm
+
+ 29.7cm
+ 21cm
+ 2cm
+ 2cm
+ 2cm
+ 2cm
+
+ Cm
+
diff --git a/Prototyping/src/Tables/Tab50100.CKRecipeLine.al b/Prototyping/src/Tables/Tab50100.CKRecipeLine.al
new file mode 100644
index 0000000..ef27e76
--- /dev/null
+++ b/Prototyping/src/Tables/Tab50100.CKRecipeLine.al
@@ -0,0 +1,66 @@
+table 50100 "CK Recipe Line"
+{
+ Caption = 'Color Kitchen Recipe Line';
+ DataClassification = CustomerContent;
+ LookupPageId = "CK Recipe Page";
+ DrillDownPageId = "CK Recipe Page";
+
+ fields
+ {
+ field(1; "Item No."; Code[20])
+ {
+ Caption = 'Item No.';
+ TableRelation = Item;
+ NotBlank = true;
+ }
+ field(2; "Line No."; Integer)
+ {
+ Caption = 'Line No.';
+ }
+ field(3; "Base Component Item No."; Code[20])
+ {
+ Caption = 'Base Component Item No.';
+ TableRelation = Item;
+ NotBlank = true;
+
+ trigger OnValidate()
+ var
+ ComponentItem: Record Item;
+ begin
+ if ComponentItem.Get(Rec."Base Component Item No.") then
+ Rec."Base Component Description" := ComponentItem.Description
+ else
+ Rec."Base Component Description" := '';
+ end;
+ }
+ field(4; "Base Component Description"; Text[100])
+ {
+ Caption = 'Description';
+ }
+ field(5; Percentage; Decimal)
+ {
+ Caption = 'Percentage (%)';
+ DecimalPlaces = 2 : 5;
+ MinValue = 0;
+ MaxValue = 100;
+ }
+ }
+
+ keys
+ {
+ key(PK; "Item No.", "Line No.")
+ {
+ Clustered = true;
+ }
+ }
+
+ procedure GetNextLineNo(ItemNo: Code[20]): Integer
+ var
+ RecipeLine: Record "CK Recipe Line";
+ begin
+ RecipeLine.SetRange("Item No.", ItemNo);
+ if RecipeLine.FindLast() then
+ exit(RecipeLine."Line No." + 10000);
+ exit(10000);
+ end;
+}
diff --git a/Prototyping/src/Tables/Tab50101.CKMixHeader.al b/Prototyping/src/Tables/Tab50101.CKMixHeader.al
new file mode 100644
index 0000000..f3f495d
--- /dev/null
+++ b/Prototyping/src/Tables/Tab50101.CKMixHeader.al
@@ -0,0 +1,107 @@
+table 50101 "CK Mix Header"
+{
+ Caption = 'Color Kitchen Mix Header';
+ DataClassification = CustomerContent;
+ LookupPageId = "CK Page";
+ DrillDownPageId = "CK Page";
+
+ fields
+ {
+ field(1; "Entry No."; Integer)
+ {
+ Caption = 'Entry No.';
+ AutoIncrement = true;
+ }
+ field(2; "PVS Case No."; Integer)
+ {
+ Caption = 'Case No.';
+ }
+ field(3; "PVS Job No."; Integer)
+ {
+ Caption = 'Job No.';
+ }
+ field(4; "Item No."; Code[20])
+ {
+ Caption = 'Mixed Ink Item No.';
+ TableRelation = Item;
+ NotBlank = true;
+
+ trigger OnValidate()
+ var
+ MixedInkItem: Record Item;
+ begin
+ if MixedInkItem.Get(Rec."Item No.") then begin
+ Rec."Item Description" := MixedInkItem.Description;
+ Rec."Unit of Measure Code" := MixedInkItem."Base Unit of Measure";
+ end else begin
+ Rec."Item Description" := '';
+ Rec."Unit of Measure Code" := '';
+ end;
+ end;
+ }
+ field(5; "Item Description"; Text[100])
+ {
+ Caption = 'Description';
+ }
+ field(6; "Quantity to Mix"; Decimal)
+ {
+ Caption = 'Quantity to Mix';
+ DecimalPlaces = 0 : 5;
+ MinValue = 0;
+ }
+ field(7; "Quantity Mixed"; Decimal)
+ {
+ Caption = 'Quantity Mixed';
+ DecimalPlaces = 0 : 5;
+ MinValue = 0;
+ }
+ field(8; Status; Enum "CK Mix Status")
+ {
+ Caption = 'Status';
+ }
+ field(9; "LOT No."; Code[50])
+ {
+ Caption = 'LOT No.';
+ }
+ field(10; "Created At"; DateTime)
+ {
+ Caption = 'Created At';
+ }
+ field(11; "Unit of Measure Code"; Code[10])
+ {
+ Caption = 'Unit of Measure';
+ TableRelation = "Unit of Measure";
+ }
+ field(12; "Eco Label Codes"; Text[250])
+ {
+ Caption = 'Eco Labels';
+ }
+ field(13; Notes; Text[250])
+ {
+ Caption = 'Notes';
+ }
+ field(14; "Quantity in Stock"; Decimal)
+ {
+ Caption = 'Quantity in Stock';
+ FieldClass = FlowField;
+ CalcFormula = sum("Item Ledger Entry".Quantity where("Item No." = field("Item No.")));
+ Editable = false;
+ }
+ }
+
+ keys
+ {
+ key(PK; "Entry No.")
+ {
+ Clustered = true;
+ }
+ key(K2; "PVS Case No.", "PVS Job No.", "Item No.")
+ {
+ }
+ }
+
+ trigger OnInsert()
+ begin
+ Rec."Created At" := CurrentDateTime();
+ end;
+}
diff --git a/Prototyping/src/Tables/Tab50102.CKMixLine.al b/Prototyping/src/Tables/Tab50102.CKMixLine.al
new file mode 100644
index 0000000..d47a4ea
--- /dev/null
+++ b/Prototyping/src/Tables/Tab50102.CKMixLine.al
@@ -0,0 +1,86 @@
+table 50102 "CK Mix Line"
+{
+ Caption = 'Color Kitchen Mix Line';
+ DataClassification = CustomerContent;
+
+ fields
+ {
+ field(1; "Mix Header Entry No."; Integer)
+ {
+ Caption = 'Mix Header Entry No.';
+ TableRelation = "CK Mix Header"."Entry No.";
+ NotBlank = true;
+ }
+ field(2; "Line No."; Integer)
+ {
+ Caption = 'Line No.';
+ }
+ field(3; "Base Component Item No."; Code[20])
+ {
+ Caption = 'Base Component Item No.';
+ TableRelation = Item;
+ NotBlank = true;
+
+ trigger OnValidate()
+ var
+ ComponentItem: Record Item;
+ begin
+ if ComponentItem.Get(Rec."Base Component Item No.") then
+ Rec."Base Component Description" := ComponentItem.Description
+ else
+ Rec."Base Component Description" := '';
+ end;
+ }
+ field(4; "Base Component Description"; Text[100])
+ {
+ Caption = 'Description';
+ }
+ field(5; "LOT No."; Code[50])
+ {
+ Caption = 'LOT No.';
+ }
+ field(6; "Quantity Required"; Decimal)
+ {
+ Caption = 'Quantity Required';
+ DecimalPlaces = 0 : 5;
+ MinValue = 0;
+ }
+ field(7; "Quantity Used"; Decimal)
+ {
+ Caption = 'Quantity Used';
+ DecimalPlaces = 0 : 5;
+ MinValue = 0;
+ }
+ field(8; Status; Enum "CK Mix Line Status")
+ {
+ Caption = 'Status';
+ }
+ field(9; Percentage; Decimal)
+ {
+ Caption = 'Percentage (%)';
+ DecimalPlaces = 2 : 5;
+ }
+ field(10; "Shelf No."; Code[20])
+ {
+ Caption = 'Shelf No.';
+ }
+ }
+
+ keys
+ {
+ key(PK; "Mix Header Entry No.", "Line No.")
+ {
+ Clustered = true;
+ }
+ }
+
+ procedure GetNextLineNo(MixHeaderEntryNo: Integer): Integer
+ var
+ MixLine: Record "CK Mix Line";
+ begin
+ MixLine.SetRange("Mix Header Entry No.", MixHeaderEntryNo);
+ if MixLine.FindLast() then
+ exit(MixLine."Line No." + 10000);
+ exit(10000);
+ end;
+}
From 2a6550420fbf1a2cfa170c5d76e1dda5c1edac67 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 23 Jun 2026 13:10:56 +0000
Subject: [PATCH 2/2] fix: address code review issues (MinValue, LOT guard,
safe filter parsing)
---
.../src/Codeunits/Cod50100.CKManagement.al | 3 ++-
Prototyping/src/Pages/Pag50100.CKRecipePage.al | 13 ++++++++++++-
Prototyping/src/Pages/Pag50103.CKMixLines.al | 18 +++++++++++++++---
Prototyping/src/Tables/Tab50102.CKMixLine.al | 1 +
4 files changed, 30 insertions(+), 5 deletions(-)
diff --git a/Prototyping/src/Codeunits/Cod50100.CKManagement.al b/Prototyping/src/Codeunits/Cod50100.CKManagement.al
index 08b71fa..c2d159d 100644
--- a/Prototyping/src/Codeunits/Cod50100.CKManagement.al
+++ b/Prototyping/src/Codeunits/Cod50100.CKManagement.al
@@ -85,7 +85,8 @@ codeunit 50100 "CK Management"
ItemJnlLine.Validate("Entry Type", ItemJnlLine."Entry Type"::"Negative Adjmt.");
ItemJnlLine.Validate("Item No.", MixLine."Base Component Item No.");
ItemJnlLine.Validate(Quantity, MixLine."Quantity Used");
- ItemJnlLine."Lot No." := MixLine."LOT No.";
+ if MixLine."LOT No." <> '' then
+ ItemJnlLine."Lot No." := MixLine."LOT No.";
ItemJnlLine."Document No." := DocumentNo;
ItemJnlLine."Source Code" := GetSourceCode();
ItemJnlPostLine.RunWithCheck(ItemJnlLine);
diff --git a/Prototyping/src/Pages/Pag50100.CKRecipePage.al b/Prototyping/src/Pages/Pag50100.CKRecipePage.al
index b84bd8f..de941cb 100644
--- a/Prototyping/src/Pages/Pag50100.CKRecipePage.al
+++ b/Prototyping/src/Pages/Pag50100.CKRecipePage.al
@@ -70,10 +70,21 @@ page 50100 "CK Recipe Page"
var
ItemFilter: Code[20];
begin
- ItemFilter := CopyStr(Rec.GetFilter("Item No."), 1, 20);
+ ItemFilter := GetSingleCodeFilter();
if ItemFilter <> '' then begin
Rec."Item No." := ItemFilter;
Rec."Line No." := Rec.GetNextLineNo(ItemFilter);
end;
end;
+
+ local procedure GetSingleCodeFilter(): Code[20]
+ var
+ FilterTxt: Text;
+ begin
+ FilterTxt := Rec.GetFilter("Item No.");
+ // Only use the filter when it represents exactly one value (no wildcards or OR)
+ if (FilterTxt <> '') and (FilterTxt.IndexOf('|') = 0) and (FilterTxt.IndexOf('*') = 0) and (FilterTxt.IndexOf('&') = 0) then
+ exit(CopyStr(FilterTxt, 1, 20));
+ exit('');
+ end;
}
diff --git a/Prototyping/src/Pages/Pag50103.CKMixLines.al b/Prototyping/src/Pages/Pag50103.CKMixLines.al
index 4541491..830f3e9 100644
--- a/Prototyping/src/Pages/Pag50103.CKMixLines.al
+++ b/Prototyping/src/Pages/Pag50103.CKMixLines.al
@@ -74,16 +74,28 @@ page 50103 "CK Mix Lines"
trigger OnNewRecord(BelowxRec: Boolean)
var
HeaderNo: Integer;
- HeaderFilter: Text;
begin
- HeaderFilter := Rec.GetFilter("Mix Header Entry No.");
- if (HeaderFilter <> '') and Evaluate(HeaderNo, HeaderFilter) then begin
+ HeaderNo := GetSingleIntFilter();
+ if HeaderNo <> 0 then begin
Rec."Mix Header Entry No." := HeaderNo;
Rec."Line No." := Rec.GetNextLineNo(HeaderNo);
end;
IsEditable := true;
end;
+ local procedure GetSingleIntFilter(): Integer
+ var
+ FilterTxt: Text;
+ HeaderNo: Integer;
+ begin
+ FilterTxt := Rec.GetFilter("Mix Header Entry No.");
+ // Only parse when the filter is a plain single integer (no ranges, wildcards, or OR)
+ if (FilterTxt <> '') and (FilterTxt.IndexOf('|') = 0) and (FilterTxt.IndexOf('.') = 0) and (FilterTxt.IndexOf('*') = 0) then
+ if Evaluate(HeaderNo, FilterTxt) then
+ exit(HeaderNo);
+ exit(0);
+ end;
+
var
LineStatusStyle: Text;
IsEditable: Boolean;
diff --git a/Prototyping/src/Tables/Tab50102.CKMixLine.al b/Prototyping/src/Tables/Tab50102.CKMixLine.al
index d47a4ea..878f2ab 100644
--- a/Prototyping/src/Tables/Tab50102.CKMixLine.al
+++ b/Prototyping/src/Tables/Tab50102.CKMixLine.al
@@ -59,6 +59,7 @@ table 50102 "CK Mix Line"
{
Caption = 'Percentage (%)';
DecimalPlaces = 2 : 5;
+ MinValue = 0;
}
field(10; "Shelf No."; Code[20])
{