From 0d37092f32bfc369d57e9d38d3774f5c00c9b655 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 23 Jun 2026 12:53:00 +0000 Subject: [PATCH 1/3] feat: AI Case Pattern Recognition & Copilot Chat for PrintVis --- Prototyping/Cod50100.PVSCaseHistoryMgmt.al | 124 ++++++++++ Prototyping/Cod50101.PVSPatternAnalysis.al | 158 +++++++++++++ Prototyping/Cod50102.PVSCopilotCaseGen.al | 211 ++++++++++++++++++ Prototyping/Cod50103.PVSAICaseInstall.al | 40 ++++ .../EnumExt50100.PVSCopilotCapabilities.al | 7 + Prototyping/Pag50100.PVSAICaseSetup.al | 164 ++++++++++++++ Prototyping/Pag50101.PVSCasePatterns.al | 135 +++++++++++ Prototyping/Pag50102.PVSCopilotCaseDialog.al | 172 ++++++++++++++ Prototyping/Pag50103.PVSCaseHistorySubform.al | 61 +++++ Prototyping/PagExt50100.PVSCaseCardExt.al | 43 ++++ Prototyping/PermSet50100.PVSAICaseUser.al | 20 ++ Prototyping/Tab50100.PVSCaseHistory.al | 99 ++++++++ Prototyping/Tab50101.PVSCasePattern.al | 84 +++++++ Prototyping/Tab50102.PVSAICaseSetup.al | 60 +++++ Prototyping/TabExt50100.PVSCaseHeaderExt.al | 23 ++ 15 files changed, 1401 insertions(+) create mode 100644 Prototyping/Cod50100.PVSCaseHistoryMgmt.al create mode 100644 Prototyping/Cod50101.PVSPatternAnalysis.al create mode 100644 Prototyping/Cod50102.PVSCopilotCaseGen.al create mode 100644 Prototyping/Cod50103.PVSAICaseInstall.al create mode 100644 Prototyping/EnumExt50100.PVSCopilotCapabilities.al create mode 100644 Prototyping/Pag50100.PVSAICaseSetup.al create mode 100644 Prototyping/Pag50101.PVSCasePatterns.al create mode 100644 Prototyping/Pag50102.PVSCopilotCaseDialog.al create mode 100644 Prototyping/Pag50103.PVSCaseHistorySubform.al create mode 100644 Prototyping/PagExt50100.PVSCaseCardExt.al create mode 100644 Prototyping/PermSet50100.PVSAICaseUser.al create mode 100644 Prototyping/Tab50100.PVSCaseHistory.al create mode 100644 Prototyping/Tab50101.PVSCasePattern.al create mode 100644 Prototyping/Tab50102.PVSAICaseSetup.al create mode 100644 Prototyping/TabExt50100.PVSCaseHeaderExt.al diff --git a/Prototyping/Cod50100.PVSCaseHistoryMgmt.al b/Prototyping/Cod50100.PVSCaseHistoryMgmt.al new file mode 100644 index 0000000..df5f169 --- /dev/null +++ b/Prototyping/Cod50100.PVSCaseHistoryMgmt.al @@ -0,0 +1,124 @@ +/// +/// Populates the PVS Case History table from historical PrintVis cases. +/// NOTE: Verify the field names below against your PVS Case Header table definition. +/// The following assumptions were made: +/// "No." - primary key +/// "Sell-to Customer No." - customer link +/// "Description" - case description +/// "Job Type" - job type code +/// "Order Date" - order/creation date +/// "Requested Delivery Date" - due date +/// "Quantity" - order quantity +/// "Total Price" - total sales price +/// "Status" - case status +/// +codeunit 50100 "PVS Case History Mgmt" +{ + Access = Public; + + trigger OnRun() + begin + PopulateHistory(); + end; + + /// + /// Clears and repopulates the Case History table with cases from the last N months + /// as configured in PVS AI Case Setup. + /// + procedure PopulateHistory() + var + Setup: Record "PVS AI Case Setup"; + CaseHistory: Record "PVS Case History"; + PVSCaseHeader: Record "PVS Case Header"; + Customer: Record Customer; + PatternAnalysis: Codeunit "PVS Pattern Analysis"; + FromDate: Date; + InsertCount: Integer; + begin + GetOrCreateSetup(Setup); + FromDate := CalcDate('<-' + Format(Setup."History Months") + 'M>', Today()); + + CaseHistory.DeleteAll(false); + + PVSCaseHeader.SetFilter(PVSCaseHeader."Order Date", '>=%1', FromDate); + if PVSCaseHeader.FindSet() then + repeat + CaseHistory.Init(); + CaseHistory."Case No." := PVSCaseHeader."No."; + CaseHistory."Customer No." := PVSCaseHeader."Sell-to Customer No."; + CaseHistory."Description" := + CopyStr(PVSCaseHeader.Description, 1, MaxStrLen(CaseHistory.Description)); + CaseHistory."Job Type Code" := PVSCaseHeader."Job Type"; + CaseHistory."Order Date" := PVSCaseHeader."Order Date"; + CaseHistory."Due Date" := PVSCaseHeader."Requested Delivery Date"; + CaseHistory."Quantity" := PVSCaseHeader.Quantity; + CaseHistory."Total Amount" := PVSCaseHeader."Total Price"; + CaseHistory."Status" := Format(PVSCaseHeader.Status); + CaseHistory."Created Date" := PVSCaseHeader."Order Date"; + + if (CaseHistory."Order Date" <> 0D) and (CaseHistory."Due Date" <> 0D) and + (CaseHistory."Due Date" >= CaseHistory."Order Date") + then + CaseHistory."Turnaround Days" := + CaseHistory."Due Date" - CaseHistory."Order Date"; + + if Customer.Get(PVSCaseHeader."Sell-to Customer No.") then begin + CaseHistory."Customer Name" := + CopyStr(Customer.Name, 1, MaxStrLen(CaseHistory."Customer Name")); + CaseHistory."Customer Segment" := Customer."Customer Posting Group"; + end; + + CaseHistory.Insert(false); + InsertCount += 1; + until PVSCaseHeader.Next() = 0; + + Setup."Total Cases Loaded" := InsertCount; + Setup."Last Refresh Date" := CurrentDateTime(); + Setup.Modify(true); + + PatternAnalysis.AnalyzePatterns(); + end; + + /// + /// Creates a recurring Job Queue Entry that refreshes case history nightly. + /// + procedure CreateJobQueueEntry() + var + Setup: Record "PVS AI Case Setup"; + JobQueueEntry: Record "Job Queue Entry"; + begin + GetOrCreateSetup(Setup); + + JobQueueEntry.Init(); + JobQueueEntry."Object Type to Run" := JobQueueEntry."Object Type to Run"::Codeunit; + JobQueueEntry."Object ID to Run" := Codeunit::"PVS Case History Mgmt"; + JobQueueEntry."Job Queue Category Code" := Setup."Job Queue Category Code"; + JobQueueEntry."Recurring Job" := true; + JobQueueEntry."No. of Minutes between Runs" := 1440; + JobQueueEntry."Starting Time" := 020000T; + JobQueueEntry."Run on Mondays" := true; + JobQueueEntry."Run on Tuesdays" := true; + JobQueueEntry."Run on Wednesdays" := true; + JobQueueEntry."Run on Thursdays" := true; + JobQueueEntry."Run on Fridays" := true; + JobQueueEntry."Run on Saturdays" := false; + JobQueueEntry."Run on Sundays" := false; + JobQueueEntry.Description := CopyStr( + 'Refresh PVS Case History (AI)', 1, MaxStrLen(JobQueueEntry.Description)); + JobQueueEntry.Insert(true); + + Codeunit.Run(Codeunit::"Job Queue - Enqueue", JobQueueEntry); + + Message('Job Queue Entry created. Case history will refresh nightly at 02:00.'); + end; + + local procedure GetOrCreateSetup(var Setup: Record "PVS AI Case Setup") + begin + if not Setup.Get('DEFAULT') then begin + Setup.Init(); + Setup.Code := 'DEFAULT'; + Setup."History Months" := 12; + Setup.Insert(true); + end; + end; +} diff --git a/Prototyping/Cod50101.PVSPatternAnalysis.al b/Prototyping/Cod50101.PVSPatternAnalysis.al new file mode 100644 index 0000000..30116fe --- /dev/null +++ b/Prototyping/Cod50101.PVSPatternAnalysis.al @@ -0,0 +1,158 @@ +/// +/// Analyses Case History records to produce pattern summaries stored in PVS Case Pattern. +/// Patterns group cases by Job Type + Material + Customer Segment and compute statistical +/// summaries (averages, min/max, confidence score) used to guide Copilot suggestions. +/// +codeunit 50101 "PVS Pattern Analysis" +{ + Access = Public; + + /// + /// Rebuilds the PVS Case Pattern table from all current PVS Case History records. + /// + procedure AnalyzePatterns() + var + Setup: Record "PVS AI Case Setup"; + CasePattern: Record "PVS Case Pattern"; + CaseHistory: Record "PVS Case History"; + TotalAmount: Decimal; + MinAmount: Decimal; + MaxAmount: Decimal; + TotalQuantity: Decimal; + TotalTurnaround: Decimal; + CaseCount: Integer; + PatternCount: Integer; + CurrentJobType: Code[20]; + CurrentMaterial: Code[20]; + CurrentSegment: Text[50]; + begin + CasePattern.DeleteAll(false); + + CaseHistory.SetCurrentKey(CaseHistory."Job Type Code", CaseHistory."Material Code", CaseHistory."Customer Segment"); + if not CaseHistory.FindSet() then + exit; + + CurrentJobType := CaseHistory."Job Type Code"; + CurrentMaterial := CaseHistory."Material Code"; + CurrentSegment := CaseHistory."Customer Segment"; + InitAccumulators(TotalAmount, MinAmount, MaxAmount, TotalQuantity, TotalTurnaround, CaseCount); + + repeat + if (CaseHistory."Job Type Code" <> CurrentJobType) or + (CaseHistory."Material Code" <> CurrentMaterial) or + (CaseHistory."Customer Segment" <> CurrentSegment) + then begin + WritePattern( + CasePattern, CurrentJobType, CurrentMaterial, CurrentSegment, + TotalAmount, MinAmount, MaxAmount, TotalQuantity, TotalTurnaround, CaseCount); + PatternCount += 1; + + CurrentJobType := CaseHistory."Job Type Code"; + CurrentMaterial := CaseHistory."Material Code"; + CurrentSegment := CaseHistory."Customer Segment"; + InitAccumulators( + TotalAmount, MinAmount, MaxAmount, TotalQuantity, TotalTurnaround, CaseCount); + end; + + AccumulateCase( + CaseHistory, TotalAmount, MinAmount, MaxAmount, + TotalQuantity, TotalTurnaround, CaseCount); + until CaseHistory.Next() = 0; + + // Write the last group + if CaseCount > 0 then begin + WritePattern( + CasePattern, CurrentJobType, CurrentMaterial, CurrentSegment, + TotalAmount, MinAmount, MaxAmount, TotalQuantity, TotalTurnaround, CaseCount); + PatternCount += 1; + end; + + if Setup.Get('DEFAULT') then begin + Setup."Total Patterns Found" := PatternCount; + Setup.Modify(true); + end; + end; + + local procedure InitAccumulators( + var TotalAmount: Decimal; + var MinAmount: Decimal; + var MaxAmount: Decimal; + var TotalQuantity: Decimal; + var TotalTurnaround: Decimal; + var CaseCount: Integer) + begin + TotalAmount := 0; + MinAmount := 0; + MaxAmount := 0; + TotalQuantity := 0; + TotalTurnaround := 0; + CaseCount := 0; + end; + + local procedure AccumulateCase( + CaseHistory: Record "PVS Case History"; + var TotalAmount: Decimal; + var MinAmount: Decimal; + var MaxAmount: Decimal; + var TotalQuantity: Decimal; + var TotalTurnaround: Decimal; + var CaseCount: Integer) + begin + CaseCount += 1; + TotalAmount += CaseHistory."Total Amount"; + TotalQuantity += CaseHistory.Quantity; + TotalTurnaround += CaseHistory."Turnaround Days"; + + if CaseCount = 1 then begin + MinAmount := CaseHistory."Total Amount"; + MaxAmount := CaseHistory."Total Amount"; + end else begin + if CaseHistory."Total Amount" < MinAmount then + MinAmount := CaseHistory."Total Amount"; + if CaseHistory."Total Amount" > MaxAmount then + MaxAmount := CaseHistory."Total Amount"; + end; + end; + + local procedure WritePattern( + var CasePattern: Record "PVS Case Pattern"; + JobTypeCode: Code[20]; + MaterialCode: Code[20]; + CustomerSegment: Text[50]; + TotalAmount: Decimal; + MinAmount: Decimal; + MaxAmount: Decimal; + TotalQuantity: Decimal; + TotalTurnaround: Decimal; + CaseCount: Integer) + begin + CasePattern.Init(); + CasePattern."Job Type Code" := JobTypeCode; + CasePattern."Material Code" := MaterialCode; + CasePattern."Customer Segment" := + CopyStr(CustomerSegment, 1, MaxStrLen(CasePattern."Customer Segment")); + CasePattern."Case Count" := CaseCount; + CasePattern."Min Total Amount" := MinAmount; + CasePattern."Max Total Amount" := MaxAmount; + CasePattern."Last Updated" := CurrentDateTime(); + + if CaseCount > 0 then begin + CasePattern."Avg Total Amount" := Round(TotalAmount / CaseCount, 0.01); + CasePattern."Avg Quantity" := Round(TotalQuantity / CaseCount, 0.01); + CasePattern."Avg Turnaround Days" := Round(TotalTurnaround / CaseCount, 0.1); + end; + + // Confidence = min(100, sqrt(CaseCount) * 20) — more cases = higher confidence + CasePattern."Confidence Score" := + Round(MinDecimal(100, Sqrt(CaseCount) * 20), 0.1); + + CasePattern.Insert(false); + end; + + local procedure MinDecimal(A: Decimal; B: Decimal): Decimal + begin + if A < B then + exit(A); + exit(B); + end; +} diff --git a/Prototyping/Cod50102.PVSCopilotCaseGen.al b/Prototyping/Cod50102.PVSCopilotCaseGen.al new file mode 100644 index 0000000..940e3f0 --- /dev/null +++ b/Prototyping/Cod50102.PVSCopilotCaseGen.al @@ -0,0 +1,211 @@ +/// +/// Handles AI-powered case generation using Azure OpenAI. +/// Uses historical patterns from PVS Case Pattern to enrich the prompt with context, +/// then calls the AOAI chat completions API and maps the response back to case fields. +/// +codeunit 50102 "PVS Copilot Case Gen" +{ + Access = Public; + + /// + /// Generates a suggested case record based on the user's natural-language prompt + /// and historical patterns. The result is stored in a temporary PVS Case History record. + /// + procedure GenerateCaseSuggestion(UserPrompt: Text; var SuggestedCase: Record "PVS Case History") + var + Setup: Record "PVS AI Case Setup"; + AzureOpenAI: Codeunit "Azure OpenAI"; + AOAIChatMessages: Codeunit "AOAI Chat Messages"; + AOAIChatCompletionParams: Codeunit "AOAI Chat Completion Params"; + AOAIOperationResponse: Codeunit "AOAI Operation Response"; + SystemPrompt: Text; + ResponseText: Text; + ApiKey: Text; + begin + if not Setup.Get('DEFAULT') then + Error('PVS AI Case Setup not found. Please configure the AI settings first.'); + if not Setup."Enable Copilot" then + Error('Copilot is not enabled. Please enable it in PVS AI Case Setup.'); + if Setup."AOAI Endpoint" = '' then + Error('Azure OpenAI Endpoint is not configured in PVS AI Case Setup.'); + if Setup."AOAI Deployment Name" = '' then + Error('Azure OpenAI Deployment Name is not configured in PVS AI Case Setup.'); + + GetApiKey(ApiKey); + if ApiKey = '' then + Error('Azure OpenAI API Key is not stored. Please enter it in PVS AI Case Setup.'); + + SystemPrompt := BuildSystemPrompt(UserPrompt); + + AzureOpenAI.SetAuthorization( + Enum::"AOAI Model Type"::"Chat Completions", + Setup."AOAI Endpoint", + Setup."AOAI Deployment Name", + ApiKey); + AzureOpenAI.SetCopilotCapability(Enum::"Copilot Capability"::"PVS Case Assistant"); + + AOAIChatCompletionParams.SetMaxTokens(800); + AOAIChatCompletionParams.SetTemperature(0.3); + + AOAIChatMessages.AddSystemMessage(SystemPrompt); + AOAIChatMessages.AddUserMessage(UserPrompt); + + AzureOpenAI.GenerateChatCompletion(AOAIChatMessages, AOAIChatCompletionParams, AOAIOperationResponse); + + if not AOAIOperationResponse.IsSuccess() then + Error('Copilot generation failed: %1', AOAIOperationResponse.GetError()); + + ResponseText := AOAIChatMessages.GetLastMessage(); + ParseAOAIResponse(ResponseText, SuggestedCase); + end; + + /// + /// Creates a new PVS Case Header from a Copilot suggestion and opens it for review. + /// NOTE: Verify field names against your PVS Case Header table definition. + /// + procedure CreateCaseFromSuggestion( + JobTypeCode: Code[20]; + CustomerNo: Code[20]; + Description: Text[200]; + MaterialCode: Code[20]; + Quantity: Decimal; + DueDays: Integer) + var + PVSCaseHeader: Record "PVS Case Header"; + begin + PVSCaseHeader.Init(); + PVSCaseHeader."Sell-to Customer No." := CustomerNo; + PVSCaseHeader."Job Type" := JobTypeCode; + PVSCaseHeader.Description := + CopyStr(Description, 1, MaxStrLen(PVSCaseHeader.Description)); + PVSCaseHeader.Quantity := Quantity; + PVSCaseHeader."Order Date" := Today(); + if DueDays > 0 then + PVSCaseHeader."Requested Delivery Date" := Today() + DueDays; + + PVSCaseHeader."Copilot Assisted" := true; + PVSCaseHeader."Copilot Assisted At" := CurrentDateTime(); + + PVSCaseHeader.Insert(true); + + Page.Run(Page::"PVS Case Card", PVSCaseHeader); + end; + + local procedure BuildSystemPrompt(UserPrompt: Text): Text + var + PatternContext: Text; + SystemPromptBuilder: TextBuilder; + begin + PatternContext := BuildPatternContext(UserPrompt); + + SystemPromptBuilder.Append( + 'You are a PrintVis case management assistant. ' + + 'Based on the historical patterns provided and the user''s request, ' + + 'generate a new case specification.' + + 'Respond ONLY with valid JSON using this exact structure:' + NewLine() + + '{' + NewLine() + + ' "job_type_code": "", ' + NewLine() + + ' "customer_no": "", ' + NewLine() + + ' "description": "", ' + NewLine() + + ' "quantity": , ' + NewLine() + + ' "material_code": "", ' + NewLine() + + ' "turnaround_days": , ' + NewLine() + + ' "estimated_amount": ' + NewLine() + + '}' + NewLine()); + + if PatternContext <> '' then begin + SystemPromptBuilder.Append(NewLine() + 'Historical patterns relevant to this request:' + NewLine()); + SystemPromptBuilder.Append(PatternContext); + end; + + exit(SystemPromptBuilder.ToText()); + end; + + local procedure BuildPatternContext(UserPrompt: Text): Text + var + CasePattern: Record "PVS Case Pattern"; + ContextBuilder: TextBuilder; + LineCount: Integer; + begin + // Return the top 5 patterns by confidence score as context for the AI + CasePattern.SetCurrentKey(CasePattern."Confidence Score"); + CasePattern.Ascending(false); + if not CasePattern.FindSet() then + exit(''); + + repeat + ContextBuilder.Append( + StrSubstNo( + '- Job Type: %1, Material: %2, Segment: %3, Avg Qty: %4, ' + + 'Avg Amount: %5, Avg Days: %6, Cases: %7' + NewLine(), + CasePattern."Job Type Code", + CasePattern."Material Code", + CasePattern."Customer Segment", + Format(CasePattern."Avg Quantity", 0, ''), + Format(CasePattern."Avg Total Amount", 0, ''), + Format(CasePattern."Avg Turnaround Days", 0, ''), + Format(CasePattern."Case Count"))); + LineCount += 1; + until (CasePattern.Next() = 0) or (LineCount >= 5); + + exit(ContextBuilder.ToText()); + end; + + local procedure ParseAOAIResponse(ResponseText: Text; var SuggestedCase: Record "PVS Case History") + var + JsonObj: JsonObject; + JsonTok: JsonToken; + CleanedResponse: Text; + StartPos: Integer; + EndPos: Integer; + begin + // Extract JSON block if the response contains surrounding text + StartPos := ResponseText.IndexOf('{'); + EndPos := ResponseText.LastIndexOf('}'); + if (StartPos > 0) and (EndPos > StartPos) then + CleanedResponse := ResponseText.Substring(StartPos, EndPos - StartPos + 1) + else + CleanedResponse := ResponseText; + + if not JsonObj.ReadFrom(CleanedResponse) then + exit; + + SuggestedCase.Init(); + + if JsonObj.Get('job_type_code', JsonTok) then + SuggestedCase."Job Type Code" := + CopyStr(JsonTok.AsValue().AsText(), 1, MaxStrLen(SuggestedCase."Job Type Code")); + + if JsonObj.Get('customer_no', JsonTok) then + SuggestedCase."Customer No." := + CopyStr(JsonTok.AsValue().AsText(), 1, MaxStrLen(SuggestedCase."Customer No.")); + + if JsonObj.Get('description', JsonTok) then + SuggestedCase.Description := + CopyStr(JsonTok.AsValue().AsText(), 1, MaxStrLen(SuggestedCase.Description)); + + if JsonObj.Get('quantity', JsonTok) then + SuggestedCase.Quantity := JsonTok.AsValue().AsDecimal(); + + if JsonObj.Get('material_code', JsonTok) then + SuggestedCase."Material Code" := + CopyStr(JsonTok.AsValue().AsText(), 1, MaxStrLen(SuggestedCase."Material Code")); + + if JsonObj.Get('turnaround_days', JsonTok) then + SuggestedCase."Turnaround Days" := JsonTok.AsValue().AsInteger(); + + if JsonObj.Get('estimated_amount', JsonTok) then + SuggestedCase."Total Amount" := JsonTok.AsValue().AsDecimal(); + end; + + local procedure GetApiKey(var ApiKey: Text) + begin + if not IsolatedStorage.Get('PVSAOAIApiKey', DataScope::Module, ApiKey) then + ApiKey := ''; + end; + + local procedure NewLine(): Text[2] + begin + exit('' + Chr(13) + Chr(10)); + end; +} diff --git a/Prototyping/Cod50103.PVSAICaseInstall.al b/Prototyping/Cod50103.PVSAICaseInstall.al new file mode 100644 index 0000000..0bd1ec4 --- /dev/null +++ b/Prototyping/Cod50103.PVSAICaseInstall.al @@ -0,0 +1,40 @@ +codeunit 50103 "PVS AI Case Install" +{ + Subtype = Install; + Access = Internal; + + trigger OnInstallAppPerDatabase() + begin + RegisterCopilotCapability(); + end; + + trigger OnInstallAppPerCompany() + begin + InitializeSetup(); + end; + + local procedure RegisterCopilotCapability() + var + CopilotCapability: Codeunit "Copilot Capability"; + LearnMoreUrl: Text[2048]; + begin + LearnMoreUrl := 'https://www.printvis.com'; + if not CopilotCapability.IsCapabilityRegistered(Enum::"Copilot Capability"::"PVS Case Assistant") then + CopilotCapability.RegisterCapability( + Enum::"Copilot Capability"::"PVS Case Assistant", + LearnMoreUrl); + end; + + local procedure InitializeSetup() + var + Setup: Record "PVS AI Case Setup"; + begin + if not Setup.Get('DEFAULT') then begin + Setup.Init(); + Setup.Code := 'DEFAULT'; + Setup."History Months" := 12; + Setup."Enable Copilot" := false; + Setup.Insert(true); + end; + end; +} diff --git a/Prototyping/EnumExt50100.PVSCopilotCapabilities.al b/Prototyping/EnumExt50100.PVSCopilotCapabilities.al new file mode 100644 index 0000000..f091b7f --- /dev/null +++ b/Prototyping/EnumExt50100.PVSCopilotCapabilities.al @@ -0,0 +1,7 @@ +enumextension 50100 "PVS Copilot Capabilities Ext" extends "Copilot Capability" +{ + value(50100; "PVS Case Assistant") + { + Caption = 'PrintVis Case Assistant'; + } +} diff --git a/Prototyping/Pag50100.PVSAICaseSetup.al b/Prototyping/Pag50100.PVSAICaseSetup.al new file mode 100644 index 0000000..339aca8 --- /dev/null +++ b/Prototyping/Pag50100.PVSAICaseSetup.al @@ -0,0 +1,164 @@ +page 50100 "PVS AI Case Setup" +{ + PageType = Card; + Caption = 'PVS AI Case Setup'; + ApplicationArea = All; + UsageCategory = Administration; + SourceTable = "PVS AI Case Setup"; + InsertAllowed = false; + DeleteAllowed = false; + + layout + { + area(Content) + { + group(General) + { + Caption = 'General'; + + field("History Months"; Rec."History Months") + { + ApplicationArea = All; + ToolTip = 'Number of months of case history to load for pattern analysis.'; + } + field("Last Refresh Date"; Rec."Last Refresh Date") + { + ApplicationArea = All; + ToolTip = 'Date and time the case history was last refreshed.'; + } + field("Total Cases Loaded"; Rec."Total Cases Loaded") + { + ApplicationArea = All; + ToolTip = 'Total number of cases loaded into the history table.'; + } + field("Total Patterns Found"; Rec."Total Patterns Found") + { + ApplicationArea = All; + ToolTip = 'Total number of patterns identified from case history.'; + } + field("Job Queue Category Code"; Rec."Job Queue Category Code") + { + ApplicationArea = All; + ToolTip = 'Job Queue Category used for the scheduled history refresh job.'; + } + } + group(CopilotSettings) + { + Caption = 'Copilot / Azure OpenAI'; + + field("Enable Copilot"; Rec."Enable Copilot") + { + ApplicationArea = All; + ToolTip = 'Enable the Copilot Case Assistant feature.'; + } + field("AOAI Endpoint"; Rec."AOAI Endpoint") + { + ApplicationArea = All; + ToolTip = 'Your Azure OpenAI endpoint URL (e.g. https://.openai.azure.com/).'; + } + field("AOAI Deployment Name"; Rec."AOAI Deployment Name") + { + ApplicationArea = All; + ToolTip = 'The deployment name of the GPT model in Azure OpenAI Studio.'; + } + field(APIKeyInput; APIKeyDisplayText) + { + ApplicationArea = All; + Caption = 'Azure OpenAI API Key'; + ExtendedDatatype = Masked; + ToolTip = 'Enter the Azure OpenAI API key. The value is stored securely and cannot be read back.'; + + trigger OnValidate() + begin + if APIKeyDisplayText <> '' then begin + IsolatedStorage.Set('PVSAOAIApiKey', APIKeyDisplayText, DataScope::Module); + APIKeyDisplayText := '********'; + Message('API Key saved securely.'); + end; + end; + } + } + } + } + + actions + { + area(Processing) + { + action(RefreshHistory) + { + ApplicationArea = All; + Caption = 'Refresh Case History'; + ToolTip = 'Reload the last N months of cases from PrintVis into the history table and re-run pattern analysis.'; + Image = Refresh; + Promoted = true; + PromotedCategory = Process; + PromotedIsBig = true; + + trigger OnAction() + var + HistoryMgmt: Codeunit "PVS Case History Mgmt"; + begin + HistoryMgmt.PopulateHistory(); + Rec.Get(Rec.Code); + Message('Case history refreshed. %1 cases loaded, %2 patterns found.', + Rec."Total Cases Loaded", Rec."Total Patterns Found"); + end; + } + action(AnalyzePatterns) + { + ApplicationArea = All; + Caption = 'Re-analyse Patterns'; + ToolTip = 'Re-run pattern analysis on the existing history without reloading cases.'; + Image = Alerts; + Promoted = true; + PromotedCategory = Process; + + trigger OnAction() + var + PatternAnalysis: Codeunit "PVS Pattern Analysis"; + begin + PatternAnalysis.AnalyzePatterns(); + Rec.Get(Rec.Code); + Message('%1 patterns found.', Rec."Total Patterns Found"); + end; + } + action(ViewPatterns) + { + ApplicationArea = All; + Caption = 'View Patterns'; + ToolTip = 'Open the Case Patterns list to inspect all discovered patterns.'; + Image = List; + RunObject = Page "PVS Case Patterns"; + } + action(CreateJobQueue) + { + ApplicationArea = All; + Caption = 'Schedule Nightly Refresh'; + ToolTip = 'Create a Job Queue Entry to refresh case history every night at 02:00.'; + Image = JobQueueEntry; + + trigger OnAction() + var + HistoryMgmt: Codeunit "PVS Case History Mgmt"; + begin + HistoryMgmt.CreateJobQueueEntry(); + end; + } + } + } + + trigger OnOpenPage() + begin + if not Rec.Get('DEFAULT') then begin + Rec.Init(); + Rec.Code := 'DEFAULT'; + Rec."History Months" := 12; + Rec.Insert(true); + end; + APIKeyDisplayText := '********'; + end; + + var + APIKeyDisplayText: Text; +} diff --git a/Prototyping/Pag50101.PVSCasePatterns.al b/Prototyping/Pag50101.PVSCasePatterns.al new file mode 100644 index 0000000..0535587 --- /dev/null +++ b/Prototyping/Pag50101.PVSCasePatterns.al @@ -0,0 +1,135 @@ +page 50101 "PVS Case Patterns" +{ + PageType = List; + Caption = 'PVS Case Patterns'; + ApplicationArea = All; + UsageCategory = Lists; + SourceTable = "PVS Case Pattern"; + Editable = false; + InsertAllowed = false; + ModifyAllowed = false; + DeleteAllowed = false; + + layout + { + area(Content) + { + repeater(Patterns) + { + field("Job Type Code"; Rec."Job Type Code") + { + ApplicationArea = All; + ToolTip = 'The job type common to this pattern.'; + } + field("Job Type Description"; Rec."Job Type Description") + { + ApplicationArea = All; + ToolTip = 'Description of the job type.'; + } + field("Material Code"; Rec."Material Code") + { + ApplicationArea = All; + ToolTip = 'The primary material associated with this pattern.'; + } + field("Customer Segment"; Rec."Customer Segment") + { + ApplicationArea = All; + ToolTip = 'Customer posting group representing the customer segment.'; + } + field("Case Count"; Rec."Case Count") + { + ApplicationArea = All; + ToolTip = 'Number of historical cases matching this pattern.'; + } + field("Avg Quantity"; Rec."Avg Quantity") + { + ApplicationArea = All; + ToolTip = 'Average order quantity for this pattern.'; + } + field("Avg Total Amount"; Rec."Avg Total Amount") + { + ApplicationArea = All; + ToolTip = 'Average total sales amount for this pattern.'; + } + field("Min Total Amount"; Rec."Min Total Amount") + { + ApplicationArea = All; + ToolTip = 'Minimum total sales amount observed for this pattern.'; + } + field("Max Total Amount"; Rec."Max Total Amount") + { + ApplicationArea = All; + ToolTip = 'Maximum total sales amount observed for this pattern.'; + } + field("Avg Turnaround Days"; Rec."Avg Turnaround Days") + { + ApplicationArea = All; + ToolTip = 'Average number of days from order date to due date.'; + } + field("Confidence Score"; Rec."Confidence Score") + { + ApplicationArea = All; + ToolTip = 'Confidence score (0-100) based on the number of matching cases.'; + StyleExpr = ConfidenceStyle; + } + field("Last Updated"; Rec."Last Updated") + { + ApplicationArea = All; + ToolTip = 'Date and time this pattern was last calculated.'; + } + } + } + area(FactBoxes) + { + part(CaseHistorySubform; "PVS Case History Subform") + { + ApplicationArea = All; + Caption = 'Matching Cases'; + SubPageLink = "Job Type Code" = field("Job Type Code"), + "Material Code" = field("Material Code"); + } + } + } + + actions + { + area(Processing) + { + action(ReanalyzePatterns) + { + ApplicationArea = All; + Caption = 'Re-analyse Patterns'; + ToolTip = 'Rebuild all patterns from current case history.'; + Image = Refresh; + Promoted = true; + PromotedCategory = Process; + + trigger OnAction() + var + PatternAnalysis: Codeunit "PVS Pattern Analysis"; + begin + PatternAnalysis.AnalyzePatterns(); + CurrPage.Update(false); + end; + } + } + } + + trigger OnAfterGetRecord() + begin + SetConfidenceStyle(); + end; + + local procedure SetConfidenceStyle() + begin + if Rec."Confidence Score" >= 70 then + ConfidenceStyle := 'Favorable' + else if Rec."Confidence Score" >= 40 then + ConfidenceStyle := 'Ambiguous' + else + ConfidenceStyle := 'Unfavorable'; + end; + + var + ConfidenceStyle: Text; +} diff --git a/Prototyping/Pag50102.PVSCopilotCaseDialog.al b/Prototyping/Pag50102.PVSCopilotCaseDialog.al new file mode 100644 index 0000000..8f9c869 --- /dev/null +++ b/Prototyping/Pag50102.PVSCopilotCaseDialog.al @@ -0,0 +1,172 @@ +page 50102 "PVS Copilot Case Dialog" +{ + PageType = PromptDialog; + Caption = 'New Case with Copilot'; + ApplicationArea = All; + IsPreview = true; + PromptMode = Prompt; + + layout + { + area(Prompt) + { + field(UserPromptField; UserPromptTxt) + { + ApplicationArea = All; + Caption = 'Describe the new case'; + MultiLine = true; + InstructionalText = 'Describe the print job you need to quote — for example: "4-page brochure, 5000 copies, coated paper, customer segment EXPORT, needed within 2 weeks".'; + } + } + area(Content) + { + group(ProposedCaseGroup) + { + Caption = 'Proposed Case Details'; + InstructionalText = 'Review and adjust the values proposed by Copilot before creating the case.'; + + field(ProposedJobTypeField; ProposedJobTypeCode) + { + ApplicationArea = All; + Caption = 'Job Type'; + ToolTip = 'Proposed job type code based on historical patterns.'; + } + field(ProposedCustomerNoField; ProposedCustomerNo) + { + ApplicationArea = All; + Caption = 'Customer No.'; + ToolTip = 'Proposed customer number (if the description included a customer reference).'; + } + field(ProposedDescriptionField; ProposedDescription) + { + ApplicationArea = All; + Caption = 'Description'; + MultiLine = true; + ToolTip = 'Proposed case description generated by Copilot.'; + } + field(ProposedQuantityField; ProposedQuantity) + { + ApplicationArea = All; + Caption = 'Quantity'; + ToolTip = 'Proposed order quantity.'; + } + field(ProposedMaterialCodeField; ProposedMaterialCode) + { + ApplicationArea = All; + Caption = 'Material Code'; + ToolTip = 'Proposed primary material code.'; + } + field(ProposedTurnaroundDaysField; ProposedTurnaroundDays) + { + ApplicationArea = All; + Caption = 'Turnaround Days'; + ToolTip = 'Proposed number of production days.'; + } + field(ProposedTotalAmountField; ProposedTotalAmount) + { + ApplicationArea = All; + Caption = 'Estimated Amount'; + ToolTip = 'Estimated total sales amount based on historical patterns.'; + } + } + } + } + + actions + { + area(SystemActions) + { + systemaction(Generate) + { + Caption = 'Generate'; + ToolTip = 'Generate a case suggestion based on your description and historical patterns.'; + + trigger OnAction() + begin + RunGeneration(); + end; + } + systemaction(Regenerate) + { + Caption = 'Regenerate'; + ToolTip = 'Request a new suggestion from Copilot.'; + + trigger OnAction() + begin + RunGeneration(); + end; + } + systemaction(OK) + { + Caption = 'Create Case'; + ToolTip = 'Accept the suggestion and create the new PrintVis case.'; + } + systemaction(Cancel) + { + Caption = 'Discard'; + ToolTip = 'Close the dialog without creating a case.'; + } + } + } + + trigger OnQueryClosePage(CloseAction: Action): Boolean + begin + if CloseAction = Action::OK then + if not HasGenerated then + Error('Please use the Generate action to get a suggestion before creating a case.'); + exit(true); + end; + + /// + /// Returns the suggested case field values to the caller after the dialog is accepted. + /// + procedure GetSuggestedValues( + var JobTypeCode: Code[20]; + var CustomerNo: Code[20]; + var Description: Text[200]; + var MaterialCode: Code[20]; + var Quantity: Decimal; + var TurnaroundDays: Integer) + begin + JobTypeCode := ProposedJobTypeCode; + CustomerNo := ProposedCustomerNo; + Description := ProposedDescription; + MaterialCode := ProposedMaterialCode; + Quantity := ProposedQuantity; + TurnaroundDays := ProposedTurnaroundDays; + end; + + local procedure RunGeneration() + var + CopilotCaseGen: Codeunit "PVS Copilot Case Gen"; + SuggestedCase: Record "PVS Case History" temporary; + begin + if UserPromptTxt = '' then + Error('Please enter a description for the new case before generating.'); + + CopilotCaseGen.GenerateCaseSuggestion(UserPromptTxt, SuggestedCase); + + ProposedJobTypeCode := SuggestedCase."Job Type Code"; + ProposedCustomerNo := SuggestedCase."Customer No."; + ProposedDescription := SuggestedCase.Description; + ProposedMaterialCode := SuggestedCase."Material Code"; + ProposedQuantity := SuggestedCase.Quantity; + ProposedTurnaroundDays := SuggestedCase."Turnaround Days"; + ProposedTotalAmount := SuggestedCase."Total Amount"; + HasGenerated := true; + + CurrPage.PromptMode := PromptMode::Content; + CurrPage.Update(false); + end; + + var + UserPromptTxt: Text; + ProposedJobTypeCode: Code[20]; + ProposedCustomerNo: Code[20]; + ProposedDescription: Text[200]; + ProposedMaterialCode: Code[20]; + ProposedQuantity: Decimal; + ProposedTurnaroundDays: Integer; + ProposedTotalAmount: Decimal; + HasGenerated: Boolean; +} diff --git a/Prototyping/Pag50103.PVSCaseHistorySubform.al b/Prototyping/Pag50103.PVSCaseHistorySubform.al new file mode 100644 index 0000000..eba18a9 --- /dev/null +++ b/Prototyping/Pag50103.PVSCaseHistorySubform.al @@ -0,0 +1,61 @@ +page 50103 "PVS Case History Subform" +{ + PageType = ListPart; + Caption = 'Case History'; + ApplicationArea = All; + SourceTable = "PVS Case History"; + Editable = false; + InsertAllowed = false; + ModifyAllowed = false; + DeleteAllowed = false; + + layout + { + area(Content) + { + repeater(History) + { + field("Case No."; Rec."Case No.") + { + ApplicationArea = All; + ToolTip = 'The PrintVis case number.'; + } + field("Customer Name"; Rec."Customer Name") + { + ApplicationArea = All; + ToolTip = 'Customer name.'; + } + field("Description"; Rec.Description) + { + ApplicationArea = All; + ToolTip = 'Case description.'; + } + field("Order Date"; Rec."Order Date") + { + ApplicationArea = All; + ToolTip = 'Order date.'; + } + field("Quantity"; Rec.Quantity) + { + ApplicationArea = All; + ToolTip = 'Order quantity.'; + } + field("Total Amount"; Rec."Total Amount") + { + ApplicationArea = All; + ToolTip = 'Total sales amount.'; + } + field("Turnaround Days"; Rec."Turnaround Days") + { + ApplicationArea = All; + ToolTip = 'Days from order to due date.'; + } + field("Copilot Assisted"; Rec."Copilot Assisted") + { + ApplicationArea = All; + ToolTip = 'Indicates the case was originally created with Copilot assistance.'; + } + } + } + } +} diff --git a/Prototyping/PagExt50100.PVSCaseCardExt.al b/Prototyping/PagExt50100.PVSCaseCardExt.al new file mode 100644 index 0000000..942f2db --- /dev/null +++ b/Prototyping/PagExt50100.PVSCaseCardExt.al @@ -0,0 +1,43 @@ +/// +/// Extends the PrintVis Case Card with a Copilot action that opens the AI case generation dialog. +/// NOTE: If the base page name differs from "PVS Case Card" in your environment, update the +/// extends clause accordingly. +/// +pageextension 50100 "PVS Case Card Ext" extends "PVS Case Card" +{ + actions + { + addlast(Processing) + { + action(NewCaseWithCopilot) + { + ApplicationArea = All; + Caption = 'New Case with Copilot'; + ToolTip = 'Use AI to draft a new case based on historical patterns and your description.'; + Image = Sparkle; + Promoted = true; + PromotedCategory = New; + PromotedIsBig = true; + + trigger OnAction() + var + CopilotDialog: Page "PVS Copilot Case Dialog"; + CopilotCaseGen: Codeunit "PVS Copilot Case Gen"; + JobTypeCode: Code[20]; + CustomerNo: Code[20]; + Description: Text[200]; + MaterialCode: Code[20]; + Quantity: Decimal; + TurnaroundDays: Integer; + begin + if CopilotDialog.RunModal() = Action::OK then begin + CopilotDialog.GetSuggestedValues( + JobTypeCode, CustomerNo, Description, MaterialCode, Quantity, TurnaroundDays); + CopilotCaseGen.CreateCaseFromSuggestion( + JobTypeCode, CustomerNo, Description, MaterialCode, Quantity, TurnaroundDays); + end; + end; + } + } + } +} diff --git a/Prototyping/PermSet50100.PVSAICaseUser.al b/Prototyping/PermSet50100.PVSAICaseUser.al new file mode 100644 index 0000000..484adab --- /dev/null +++ b/Prototyping/PermSet50100.PVSAICaseUser.al @@ -0,0 +1,20 @@ +permissionset 50100 "PVS AI Case User" +{ + Caption = 'PVS AI Case User'; + Assignable = true; + + Permissions = + tabledata "PVS Case History" = RIMD, + tabledata "PVS Case Pattern" = RIMD, + tabledata "PVS AI Case Setup" = RIMD, + table "PVS Case History" = X, + table "PVS Case Pattern" = X, + table "PVS AI Case Setup" = X, + codeunit "PVS Case History Mgmt" = X, + codeunit "PVS Pattern Analysis" = X, + codeunit "PVS Copilot Case Gen" = X, + page "PVS AI Case Setup" = X, + page "PVS Case Patterns" = X, + page "PVS Copilot Case Dialog" = X, + page "PVS Case History Subform" = X; +} diff --git a/Prototyping/Tab50100.PVSCaseHistory.al b/Prototyping/Tab50100.PVSCaseHistory.al new file mode 100644 index 0000000..590aa77 --- /dev/null +++ b/Prototyping/Tab50100.PVSCaseHistory.al @@ -0,0 +1,99 @@ +table 50100 "PVS Case History" +{ + Caption = 'PVS Case History'; + DataClassification = CustomerContent; + + fields + { + field(1; "Entry No."; Integer) + { + Caption = 'Entry No.'; + AutoIncrement = true; + } + field(2; "Case No."; Code[20]) + { + Caption = 'Case No.'; + } + field(3; "Customer No."; Code[20]) + { + Caption = 'Customer No.'; + } + field(4; "Customer Name"; Text[100]) + { + Caption = 'Customer Name'; + } + field(5; "Job Type Code"; Code[20]) + { + Caption = 'Job Type Code'; + } + field(6; "Job Type Description"; Text[100]) + { + Caption = 'Job Type Description'; + } + field(7; "Material Code"; Code[20]) + { + Caption = 'Material Code'; + } + field(8; "Material Description"; Text[50]) + { + Caption = 'Material Description'; + } + field(9; "Description"; Text[200]) + { + Caption = 'Description'; + } + field(10; "Order Date"; Date) + { + Caption = 'Order Date'; + } + field(11; "Due Date"; Date) + { + Caption = 'Due Date'; + } + field(12; "Quantity"; Decimal) + { + Caption = 'Quantity'; + DecimalPlaces = 0 : 2; + } + field(13; "Total Amount"; Decimal) + { + Caption = 'Total Amount'; + DecimalPlaces = 2 : 2; + } + field(14; "Turnaround Days"; Integer) + { + Caption = 'Turnaround Days'; + } + field(15; "Status"; Text[50]) + { + Caption = 'Status'; + } + field(16; "Created Date"; Date) + { + Caption = 'Created Date'; + } + field(17; "Customer Segment"; Text[50]) + { + Caption = 'Customer Segment'; + } + field(18; "Copilot Assisted"; Boolean) + { + Caption = 'Copilot Assisted'; + } + field(19; "Copilot Assisted At"; DateTime) + { + Caption = 'Copilot Assisted At'; + } + } + + keys + { + key(PK; "Entry No.") + { + Clustered = true; + } + key(CaseNo; "Case No.") { } + key(CustomerDate; "Customer No.", "Created Date") { } + key(JobTypeMaterial; "Job Type Code", "Material Code") { } + } +} diff --git a/Prototyping/Tab50101.PVSCasePattern.al b/Prototyping/Tab50101.PVSCasePattern.al new file mode 100644 index 0000000..e2c0395 --- /dev/null +++ b/Prototyping/Tab50101.PVSCasePattern.al @@ -0,0 +1,84 @@ +table 50101 "PVS Case Pattern" +{ + Caption = 'PVS Case Pattern'; + DataClassification = CustomerContent; + + fields + { + field(1; "Pattern ID"; Integer) + { + Caption = 'Pattern ID'; + AutoIncrement = true; + } + field(2; "Job Type Code"; Code[20]) + { + Caption = 'Job Type Code'; + } + field(3; "Job Type Description"; Text[100]) + { + Caption = 'Job Type Description'; + } + field(4; "Material Code"; Code[20]) + { + Caption = 'Material Code'; + } + field(5; "Customer Segment"; Text[50]) + { + Caption = 'Customer Segment'; + } + field(6; "Avg Quantity"; Decimal) + { + Caption = 'Avg Quantity'; + DecimalPlaces = 0 : 2; + } + field(7; "Avg Total Amount"; Decimal) + { + Caption = 'Avg Total Amount'; + DecimalPlaces = 2 : 2; + } + field(8; "Min Total Amount"; Decimal) + { + Caption = 'Min Total Amount'; + DecimalPlaces = 2 : 2; + } + field(9; "Max Total Amount"; Decimal) + { + Caption = 'Max Total Amount'; + DecimalPlaces = 2 : 2; + } + field(10; "Avg Turnaround Days"; Decimal) + { + Caption = 'Avg Turnaround Days'; + DecimalPlaces = 1 : 1; + } + field(11; "Case Count"; Integer) + { + Caption = 'Case Count'; + } + field(12; "Confidence Score"; Decimal) + { + Caption = 'Confidence Score (%)'; + DecimalPlaces = 0 : 1; + MinValue = 0; + MaxValue = 100; + } + field(13; "Last Updated"; DateTime) + { + Caption = 'Last Updated'; + } + field(14; "Common Description"; Text[200]) + { + Caption = 'Common Description'; + } + } + + keys + { + key(PK; "Pattern ID") + { + Clustered = true; + } + key(JobTypeMaterialSegment; "Job Type Code", "Material Code", "Customer Segment") { } + key(ConfidenceScore; "Confidence Score") { } + } +} diff --git a/Prototyping/Tab50102.PVSAICaseSetup.al b/Prototyping/Tab50102.PVSAICaseSetup.al new file mode 100644 index 0000000..121efe9 --- /dev/null +++ b/Prototyping/Tab50102.PVSAICaseSetup.al @@ -0,0 +1,60 @@ +table 50102 "PVS AI Case Setup" +{ + Caption = 'PVS AI Case Setup'; + DataClassification = CustomerContent; + + fields + { + field(1; "Code"; Code[10]) + { + Caption = 'Code'; + } + field(2; "History Months"; Integer) + { + Caption = 'History Months'; + InitValue = 12; + MinValue = 1; + MaxValue = 60; + } + field(3; "Last Refresh Date"; DateTime) + { + Caption = 'Last Refresh Date'; + Editable = false; + } + field(4; "AOAI Endpoint"; Text[250]) + { + Caption = 'Azure OpenAI Endpoint'; + } + field(5; "AOAI Deployment Name"; Text[100]) + { + Caption = 'Azure OpenAI Deployment Name'; + } + field(6; "Enable Copilot"; Boolean) + { + Caption = 'Enable Copilot'; + } + field(7; "Job Queue Category Code"; Code[10]) + { + Caption = 'Job Queue Category Code'; + TableRelation = "Job Queue Category".Code; + } + field(8; "Total Cases Loaded"; Integer) + { + Caption = 'Total Cases Loaded'; + Editable = false; + } + field(9; "Total Patterns Found"; Integer) + { + Caption = 'Total Patterns Found'; + Editable = false; + } + } + + keys + { + key(PK; "Code") + { + Clustered = true; + } + } +} diff --git a/Prototyping/TabExt50100.PVSCaseHeaderExt.al b/Prototyping/TabExt50100.PVSCaseHeaderExt.al new file mode 100644 index 0000000..560d50a --- /dev/null +++ b/Prototyping/TabExt50100.PVSCaseHeaderExt.al @@ -0,0 +1,23 @@ +/// +/// Extends the PrintVis Case Header table with Copilot audit fields. +/// NOTE: If the base table name differs from "PVS Case Header" in your environment, +/// update the extends clause accordingly. +/// +tableextension 50100 "PVS Case Header Ext" extends "PVS Case Header" +{ + fields + { + field(50100; "Copilot Assisted"; Boolean) + { + Caption = 'Copilot Assisted'; + DataClassification = CustomerContent; + ToolTip = 'Indicates this case was created with the PrintVis Copilot assistant.'; + } + field(50101; "Copilot Assisted At"; DateTime) + { + Caption = 'Copilot Assisted At'; + DataClassification = CustomerContent; + ToolTip = 'Date and time the Copilot assistant was used to create this case.'; + } + } +} From 0a72bd5b820d9d446268165a109958e3ef181844 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 23 Jun 2026 12:58:32 +0000 Subject: [PATCH 2/3] fix: resolve compile errors in copilot case objects --- Prototyping/Cod50100.PVSCaseHistoryMgmt.al | 4 ++-- Prototyping/Cod50102.PVSCopilotCaseGen.al | 6 +++--- Prototyping/Pag50102.PVSCopilotCaseDialog.al | 1 + Prototyping/TabExt50100.PVSCaseHeaderExt.al | 6 +++--- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Prototyping/Cod50100.PVSCaseHistoryMgmt.al b/Prototyping/Cod50100.PVSCaseHistoryMgmt.al index df5f169..97194e0 100644 --- a/Prototyping/Cod50100.PVSCaseHistoryMgmt.al +++ b/Prototyping/Cod50100.PVSCaseHistoryMgmt.al @@ -1,6 +1,6 @@ /// /// Populates the PVS Case History table from historical PrintVis cases. -/// NOTE: Verify the field names below against your PVS Case Header table definition. +/// NOTE: Verify the field names below against your PVS Case table definition. /// The following assumptions were made: /// "No." - primary key /// "Sell-to Customer No." - customer link @@ -29,7 +29,7 @@ codeunit 50100 "PVS Case History Mgmt" var Setup: Record "PVS AI Case Setup"; CaseHistory: Record "PVS Case History"; - PVSCaseHeader: Record "PVS Case Header"; + PVSCaseHeader: Record "PVS Case"; Customer: Record Customer; PatternAnalysis: Codeunit "PVS Pattern Analysis"; FromDate: Date; diff --git a/Prototyping/Cod50102.PVSCopilotCaseGen.al b/Prototyping/Cod50102.PVSCopilotCaseGen.al index 940e3f0..7f215c9 100644 --- a/Prototyping/Cod50102.PVSCopilotCaseGen.al +++ b/Prototyping/Cod50102.PVSCopilotCaseGen.al @@ -60,8 +60,8 @@ codeunit 50102 "PVS Copilot Case Gen" end; /// - /// Creates a new PVS Case Header from a Copilot suggestion and opens it for review. - /// NOTE: Verify field names against your PVS Case Header table definition. + /// Creates a new PVS Case from a Copilot suggestion and opens it for review. + /// NOTE: Verify field names against your PVS Case table definition. /// procedure CreateCaseFromSuggestion( JobTypeCode: Code[20]; @@ -71,7 +71,7 @@ codeunit 50102 "PVS Copilot Case Gen" Quantity: Decimal; DueDays: Integer) var - PVSCaseHeader: Record "PVS Case Header"; + PVSCaseHeader: Record "PVS Case"; begin PVSCaseHeader.Init(); PVSCaseHeader."Sell-to Customer No." := CustomerNo; diff --git a/Prototyping/Pag50102.PVSCopilotCaseDialog.al b/Prototyping/Pag50102.PVSCopilotCaseDialog.al index 8f9c869..0c2c582 100644 --- a/Prototyping/Pag50102.PVSCopilotCaseDialog.al +++ b/Prototyping/Pag50102.PVSCopilotCaseDialog.al @@ -1,6 +1,7 @@ page 50102 "PVS Copilot Case Dialog" { PageType = PromptDialog; + Extensible = false; Caption = 'New Case with Copilot'; ApplicationArea = All; IsPreview = true; diff --git a/Prototyping/TabExt50100.PVSCaseHeaderExt.al b/Prototyping/TabExt50100.PVSCaseHeaderExt.al index 560d50a..a4c524c 100644 --- a/Prototyping/TabExt50100.PVSCaseHeaderExt.al +++ b/Prototyping/TabExt50100.PVSCaseHeaderExt.al @@ -1,9 +1,9 @@ /// -/// Extends the PrintVis Case Header table with Copilot audit fields. -/// NOTE: If the base table name differs from "PVS Case Header" in your environment, +/// Extends the PrintVis Case table with Copilot audit fields. +/// NOTE: If the base table name differs from "PVS Case" in your environment, /// update the extends clause accordingly. /// -tableextension 50100 "PVS Case Header Ext" extends "PVS Case Header" +tableextension 50100 "PVS Case Header Ext" extends "PVS Case" { fields { From 708a9054ae615a42692384515c6d40530cb9ecd1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 23 Jun 2026 13:11:05 +0000 Subject: [PATCH 3/3] fix: resolve AL compile errors - correct PVS Case field names, replace Sqrt/Chr --- Prototyping/Cod50100.PVSCaseHistoryMgmt.al | 27 ++++++---------------- Prototyping/Cod50101.PVSPatternAnalysis.al | 2 +- Prototyping/Cod50102.PVSCopilotCaseGen.al | 17 +++++++------- 3 files changed, 16 insertions(+), 30 deletions(-) diff --git a/Prototyping/Cod50100.PVSCaseHistoryMgmt.al b/Prototyping/Cod50100.PVSCaseHistoryMgmt.al index 97194e0..3b443bc 100644 --- a/Prototyping/Cod50100.PVSCaseHistoryMgmt.al +++ b/Prototyping/Cod50100.PVSCaseHistoryMgmt.al @@ -1,16 +1,5 @@ /// /// Populates the PVS Case History table from historical PrintVis cases. -/// NOTE: Verify the field names below against your PVS Case table definition. -/// The following assumptions were made: -/// "No." - primary key -/// "Sell-to Customer No." - customer link -/// "Description" - case description -/// "Job Type" - job type code -/// "Order Date" - order/creation date -/// "Requested Delivery Date" - due date -/// "Quantity" - order quantity -/// "Total Price" - total sales price -/// "Status" - case status /// codeunit 50100 "PVS Case History Mgmt" { @@ -44,15 +33,13 @@ codeunit 50100 "PVS Case History Mgmt" if PVSCaseHeader.FindSet() then repeat CaseHistory.Init(); - CaseHistory."Case No." := PVSCaseHeader."No."; - CaseHistory."Customer No." := PVSCaseHeader."Sell-to Customer No."; - CaseHistory."Description" := - CopyStr(PVSCaseHeader.Description, 1, MaxStrLen(CaseHistory.Description)); - CaseHistory."Job Type Code" := PVSCaseHeader."Job Type"; + CaseHistory."Case No." := CopyStr(Format(PVSCaseHeader.ID), 1, MaxStrLen(CaseHistory."Case No.")); + CaseHistory."Customer No." := PVSCaseHeader."Customer No."; + CaseHistory."Job Type Code" := PVSCaseHeader."Job Type Code"; CaseHistory."Order Date" := PVSCaseHeader."Order Date"; - CaseHistory."Due Date" := PVSCaseHeader."Requested Delivery Date"; - CaseHistory."Quantity" := PVSCaseHeader.Quantity; - CaseHistory."Total Amount" := PVSCaseHeader."Total Price"; + CaseHistory."Due Date" := PVSCaseHeader."Due Date"; + CaseHistory."Quantity" := PVSCaseHeader."Quantity 1"; + CaseHistory."Total Amount" := PVSCaseHeader."Total Sales Price"; CaseHistory."Status" := Format(PVSCaseHeader.Status); CaseHistory."Created Date" := PVSCaseHeader."Order Date"; @@ -62,7 +49,7 @@ codeunit 50100 "PVS Case History Mgmt" CaseHistory."Turnaround Days" := CaseHistory."Due Date" - CaseHistory."Order Date"; - if Customer.Get(PVSCaseHeader."Sell-to Customer No.") then begin + if Customer.Get(PVSCaseHeader."Customer No.") then begin CaseHistory."Customer Name" := CopyStr(Customer.Name, 1, MaxStrLen(CaseHistory."Customer Name")); CaseHistory."Customer Segment" := Customer."Customer Posting Group"; diff --git a/Prototyping/Cod50101.PVSPatternAnalysis.al b/Prototyping/Cod50101.PVSPatternAnalysis.al index 30116fe..f1c10d2 100644 --- a/Prototyping/Cod50101.PVSPatternAnalysis.al +++ b/Prototyping/Cod50101.PVSPatternAnalysis.al @@ -144,7 +144,7 @@ codeunit 50101 "PVS Pattern Analysis" // Confidence = min(100, sqrt(CaseCount) * 20) — more cases = higher confidence CasePattern."Confidence Score" := - Round(MinDecimal(100, Sqrt(CaseCount) * 20), 0.1); + Round(MinDecimal(100, Power(CaseCount, 0.5) * 20), 0.1); CasePattern.Insert(false); end; diff --git a/Prototyping/Cod50102.PVSCopilotCaseGen.al b/Prototyping/Cod50102.PVSCopilotCaseGen.al index 7f215c9..950675f 100644 --- a/Prototyping/Cod50102.PVSCopilotCaseGen.al +++ b/Prototyping/Cod50102.PVSCopilotCaseGen.al @@ -61,7 +61,6 @@ codeunit 50102 "PVS Copilot Case Gen" /// /// Creates a new PVS Case from a Copilot suggestion and opens it for review. - /// NOTE: Verify field names against your PVS Case table definition. /// procedure CreateCaseFromSuggestion( JobTypeCode: Code[20]; @@ -74,14 +73,12 @@ codeunit 50102 "PVS Copilot Case Gen" PVSCaseHeader: Record "PVS Case"; begin PVSCaseHeader.Init(); - PVSCaseHeader."Sell-to Customer No." := CustomerNo; - PVSCaseHeader."Job Type" := JobTypeCode; - PVSCaseHeader.Description := - CopyStr(Description, 1, MaxStrLen(PVSCaseHeader.Description)); - PVSCaseHeader.Quantity := Quantity; + PVSCaseHeader."Customer No." := CustomerNo; + PVSCaseHeader."Job Type Code" := JobTypeCode; + PVSCaseHeader."Quantity 1" := Quantity; PVSCaseHeader."Order Date" := Today(); if DueDays > 0 then - PVSCaseHeader."Requested Delivery Date" := Today() + DueDays; + PVSCaseHeader."Due Date" := Today() + DueDays; PVSCaseHeader."Copilot Assisted" := true; PVSCaseHeader."Copilot Assisted At" := CurrentDateTime(); @@ -204,8 +201,10 @@ codeunit 50102 "PVS Copilot Case Gen" ApiKey := ''; end; - local procedure NewLine(): Text[2] + local procedure NewLine(): Text + var + TypeHelper: Codeunit "Type Helper"; begin - exit('' + Chr(13) + Chr(10)); + exit(TypeHelper.NewLine()); end; }