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]) {