diff --git a/Product Configuration Enhancement/Product Configuration Enhancement/.gitignore b/Product Configuration Enhancement/Product Configuration Enhancement/.gitignore
new file mode 100644
index 0000000..aeefae1
--- /dev/null
+++ b/Product Configuration Enhancement/Product Configuration Enhancement/.gitignore
@@ -0,0 +1,7 @@
+#This file lists which files should be ignored by git
+*.app
+rad.json
+.snapshots/
+.alcache/
+.altestrunner/
+.vscode/launch.json
diff --git a/Product Configuration Enhancement/Product Configuration Enhancement/.vscode/settings.json b/Product Configuration Enhancement/Product Configuration Enhancement/.vscode/settings.json
new file mode 100644
index 0000000..c023ecd
--- /dev/null
+++ b/Product Configuration Enhancement/Product Configuration Enhancement/.vscode/settings.json
@@ -0,0 +1,9 @@
+{
+ "al.enableCodeAnalysis": true,
+ "al.codeAnalyzers": [
+ "${CodeCop}",
+ "${UICop}",
+ "${PerTenantExtensionCop}"
+ ],
+ "al.packageCachePath": ".alpackages"
+}
\ No newline at end of file
diff --git a/Product Configuration Enhancement/Product Configuration Enhancement/Documentation.md b/Product Configuration Enhancement/Product Configuration Enhancement/Documentation.md
new file mode 100644
index 0000000..f221b45
--- /dev/null
+++ b/Product Configuration Enhancement/Product Configuration Enhancement/Documentation.md
@@ -0,0 +1,136 @@
+# Product Configuration Enhancement – End-User Documentation
+
+## Overview
+
+**Product Configuration Enhancement** is an add-on for PrintVis that lets you define
+detailed, reusable **Job Item templates** directly on the Product Card. When a product is
+applied to a Job, these templates are automatically copied onto the Job's Job Items,
+saving time on repetitive manual setup for multi-component products (for example, a book
+with a cover and inner pages, or an envelope with several sub-parts).
+
+It also exposes the PrintVis **Job User Fields** setup on the Product Card, so User Field
+values can be prepared once on the product and automatically carried over to every Job
+that uses it.
+
+## Where to find it
+
+Everything is added to the standard **PrintVis Product Card** page:
+
+- A new **Product Job Items** section (below the "Inks and Consumables" area).
+- A **User Fields** action group (next to the "Additional Info" actions).
+
+No new menu items or role center entries are required — open any Product Card as usual.
+
+## Product Job Items
+
+The **Product Job Items** part on the Product Card lets you build one or more template
+lines describing the components of the product (e.g. "Cover", "Text pages", "Insert").
+Each line is a full template for one Job Item, including size, paper, colors, tools,
+finishing and more.
+
+### Fields on a Product Job Item line
+
+| Field | Description |
+|---|---|
+| Job Item No. | Sequential number identifying the line. Assigned automatically for new lines. |
+| Component Type | Classifies how this job item is treated in production calculation (e.g. Cover, Text). |
+| No. of Pages | Total number of pages for this component. Must be an even number. |
+| Pages with Print | Number of pages that carry print. |
+| Format Code | Optional standard format (paper/substrate/plate size) to apply. When set, it fills in Format1/Format2 automatically according to the general Format Entry setup. |
+| Format1 / Format2 | Manual size values, used when no Format Code is selected. |
+| Weight | Paper/substrate weight. |
+| Imposition Code | Links a specific imposition type; also used to derive Format1/Format2 automatically for pages-on-sheet layouts. |
+| Paper No. | The paper/substrate item. Selecting an item automatically pulls its weight (if defined). |
+| Paper Description | Read-only description of the selected paper item. |
+| Finishing | Finishing type to add to estimations for this component. |
+| Colors Front / Colors Back | Number of print colors on the front and back side. |
+| Tool 1–4 | Tools required when re-running this component. |
+| Media Type | Optional media classification (e.g. Envelope, Envelope Plain, Envelope Window). |
+| List of Units | The calculation unit (cost center/machine) this component is produced on. |
+| Configuration | The Cost Center Configuration to use for the List of Units. Resolved automatically when only one configuration matches the component's size/weight; if several match, you are prompted to choose. |
+| Envelope Window Shape Type / Size X / Size Y | Envelope window details, relevant only for envelope media types. |
+| Opacity / Opacity Level | Opacity classification and level of the substrate. |
+
+### Working with Product Job Item lines
+
+- **New Job Item** – adds a blank template line to the product. Header values (format,
+ paper, colors, tools, etc.) already set on the Product itself are copied in as a
+ starting point.
+- **Copy Job Item** – duplicates the currently selected line as a new line, useful when
+ several components are very similar.
+- **Colors / Materials** – opens the ink/consumable lines (colors, coverage, washups,
+ plates, etc.) for the selected job item. You can also drill down directly from the
+ **Colors Front** or **Colors Back** fields to open the same list.
+- Deleting a line removes that component's template; it does not affect Job Items already
+ created on existing Jobs.
+
+### Colors / Materials
+
+Each Product Job Item can have its own set of ink/consumable lines, matching the standard
+PrintVis "Inks and Consumables" concept but scoped per component:
+
+| Field | Description |
+|---|---|
+| Item Quality Code | Quality of the ink/consumable. |
+| Item No. | The ink/consumable item. Lookup is restricted to items matching the selected Item Quality Code, if any. |
+| Text / Text 2 | Description(s), pulled from the item and editable. |
+| F/B | Whether the color prints on the Front, Back, or Both sides. |
+| Type | Base Color, Spot Color, or Other. |
+| Coverage Pct. | Percentage of the sheet covered at 100% coverage. |
+| Area Per Weight | Area covered per unit of ink at 100% coverage. |
+| No. of Washups | Number of color changes/washups to count. |
+
+New lines automatically inherit the Product Code and Job Item No. of the component you
+opened them from.
+
+## User Fields on the Product Card
+
+Three new actions, **User Fields 1**, **User Fields 2**, and **User Fields 3**, are added
+next to the standard Additional Info actions. Each opens the combined user field editor
+for the product, using the same field/option definitions configured for **Job User
+Fields** in PrintVis General Setup.
+
+- Each action is only visible if the corresponding Job User Fields group (1, 2, or 3) is
+ enabled in **General Setup**.
+- Shortcut keys: Shift+Ctrl+F1, Shift+Ctrl+F2, Shift+Ctrl+F3.
+- Values entered here are stored against the product and are automatically copied to the
+ Job's user fields the next time this product is applied to a Job (see below).
+
+## What happens when a product is applied to a Job
+
+When you set/change the **Product Code** on a PVS Job, the following happens
+automatically, without any extra steps:
+
+1. **Header fields** – if the product has a Format Code and/or Colors Front/Back set,
+ these are copied onto the Job header.
+2. **User Fields** – any values entered via User Fields 1/2/3 on the product are copied
+ onto the Job's user fields.
+3. **Job Items** – every Product Job Item line on the product is matched to a Job Item on
+ the Job (by Job Item No.):
+ - If a matching Job Item does not yet exist, a new sheet and Job Item are created.
+ - If it exists, its fields are updated wherever the template line has a value set
+ (component type, page counts, size, paper, colors, tools, media/envelope/opacity
+ settings, list of units/configuration).
+ - Fields left blank on the template are not overwritten, so manual adjustments already
+ made on the Job are preserved where the template doesn't specify a value.
+ - Any extra Job Items on the Job that don't correspond to a template line are left
+ untouched.
+
+This means the recommended workflow is:
+
+1. Build out the product's components once, on the Product Card, using **Product Job
+ Items**.
+2. Set up User Fields on the product if applicable.
+3. Apply the product to any Job as usual — sizes, paper, colors, tools and user fields for
+ every component are filled in automatically.
+
+## Tips
+
+- Set up **Format Code** or **Imposition Code** on a template line instead of manual
+ Format1/Format2 where possible, so sizes stay consistent if the underlying master data
+ changes.
+- Use **Copy Job Item** to quickly create near-identical components (e.g. multiple insert
+ types) and then adjust only the differing fields.
+- If **Configuration** shows blank after choosing a **List of Units**, either no
+ configuration matches the current size/weight, or more than one does — in the latter
+ case, drill down into **Configuration** to pick the correct one manually.
diff --git a/Product Configuration Enhancement/Product Configuration Enhancement/ProductConfig.permissionset.al b/Product Configuration Enhancement/Product Configuration Enhancement/ProductConfig.permissionset.al
new file mode 100644
index 0000000..cc693ab
--- /dev/null
+++ b/Product Configuration Enhancement/Product Configuration Enhancement/ProductConfig.permissionset.al
@@ -0,0 +1,9 @@
+permissionset 75000 ProductConfig
+{
+ Assignable = true;
+ Permissions = tabledata "PTE Product Job Item" = RIMD,
+ table "PTE Product Job Item" = X,
+ codeunit "PTE Product Template Mgt" = X,
+ page "PTE Product Job Item Colors" = X,
+ page "PTE Product Job Item Sub" = X;
+}
\ No newline at end of file
diff --git a/Product Configuration Enhancement/Product Configuration Enhancement/app.json b/Product Configuration Enhancement/Product Configuration Enhancement/app.json
new file mode 100644
index 0000000..0be1a44
--- /dev/null
+++ b/Product Configuration Enhancement/Product Configuration Enhancement/app.json
@@ -0,0 +1,44 @@
+{
+ "id": "8d206bcf-bc23-4041-9c54-ccfeef79998d",
+ "name": "Product Configuration Enhancement",
+ "publisher": "PrintVis A/S",
+ "version": "1.0.0.0",
+ "brief": "Product Job Item templates and user field setup for PrintVis products.",
+ "description": "Adds Product Job Item template lines to the PrintVis Product Card and syncs them to Job Items when a product is applied to a case. Also surfaces the PrintVis Job user fields on the Product Card.",
+ "privacyStatement": "http://www.printvis.com",
+ "EULA": "http://www.printvis.com",
+ "help": "https://learn.printvis.com/",
+ "url": "http://www.printvis.com",
+ "dependencies": [
+ {
+ "id": "5452f323-059e-499a-9753-5d2c07eef904",
+ "name": "PrintVis",
+ "publisher": "PrintVis A/S",
+ "version": "28.0.0.0"
+ }
+ ],
+ "screenshots": [],
+ "idRanges": [
+ {
+ "from": 75000,
+ "to": 75100
+ }
+ ],
+ "application": "28.0.0.0",
+ "platform": "28.0.0.0",
+
+ "contextSensitiveHelpUrl": "https://learn.printvis.com/",
+ "resourceExposurePolicy": {
+ "allowDebugging": true,
+ "allowDownloadingSource": false,
+ "includeSourceInSymbolFile": true
+ },
+ "runtime": "15.0",
+ "target": "Cloud",
+ "features": [
+ "TranslationFile"
+ ],
+ "supportedLocales": [
+ "en-US"
+ ]
+}
diff --git a/Product Configuration Enhancement/Product Configuration Enhancement/src/Extensions/PTEProductCardExt.PageExt.al b/Product Configuration Enhancement/Product Configuration Enhancement/src/Extensions/PTEProductCardExt.PageExt.al
new file mode 100644
index 0000000..61dbca7
--- /dev/null
+++ b/Product Configuration Enhancement/Product Configuration Enhancement/src/Extensions/PTEProductCardExt.PageExt.al
@@ -0,0 +1,109 @@
+pageextension 75006 "PTE Product Card Ext" extends "PVS Product Card"
+{
+ layout
+ {
+ // Hide the base "Inks and Consumables" product colors part; the Product Job Item
+ // sub page below replaces it for job-item based colors/materials configuration.
+ modify(InksAndConsumables)
+ {
+ Visible = false;
+ }
+
+ addafter(InksAndConsumables)
+ {
+ part(PTE_ProductJobItemSub; "PTE Product Job Item Sub")
+ {
+ ApplicationArea = All;
+ Caption = 'Product Job Items';
+ SubPageLink = "Product Code" = field("Code");
+ }
+ }
+ }
+
+ actions
+ {
+ addafter(AdditionalInfo)
+ {
+ group("PTE_UserFields")
+ {
+ Caption = 'User Fields';
+ action("PTE_PUSH_USERFIELDS_1")
+ {
+ ApplicationArea = All;
+ Caption = 'User Fields 1';
+ Image = PeriodStatus;
+ Promoted = true;
+ PromotedCategory = Process;
+ ShortCutKey = 'Shift+Ctrl+F1';
+ ToolTip = 'User Fields (1) JOB';
+ Visible = PUSH_USERFIELDS_1Visible;
+
+ trigger OnAction()
+ var
+ UserFieldMgt: Codeunit "PVS Userfield Management";
+ Code1: Code[20];
+ begin
+ Code1 := CopyStr(Rec.Code, 1, MaxStrLen(Code1));
+ UserFieldMgt.Form_Userfield_Edit(6010313, 0, '', Code1, 0, 0, 0, 0, 0);
+ end;
+ }
+ action("PTE_PUSH_USERFIELDS_2")
+ {
+ ApplicationArea = All;
+ Caption = 'User Fields 2';
+ Image = ReopenPeriod;
+ Promoted = true;
+ PromotedCategory = Process;
+ ShortCutKey = 'Shift+Ctrl+F2';
+ ToolTip = 'User Fields (2) JOB';
+ Visible = PUSH_USERFIELDS_2Visible;
+
+ trigger OnAction()
+ var
+ UserFieldMgt: Codeunit "PVS Userfield Management";
+ Code1: Code[20];
+ begin
+ Code1 := CopyStr(Rec.Code, 1, MaxStrLen(Code1));
+ UserFieldMgt.Form_Userfield_Edit(6010313, 1, '', Code1, 0, 0, 0, 0, 0);
+ end;
+ }
+ action("PTE_PUSH_USERFIELDS_3")
+ {
+ ApplicationArea = All;
+ Caption = 'User Fields 3';
+ Image = ClosePeriod;
+ Promoted = true;
+ PromotedCategory = Process;
+ ShortCutKey = 'Shift+Ctrl+F3';
+ ToolTip = 'User Fields (3) JOB';
+ Visible = PUSH_USERFIELDS_3Visible;
+
+ trigger OnAction()
+ var
+ UserFieldMgt: Codeunit "PVS Userfield Management";
+ Code1: Code[20];
+ begin
+ Code1 := CopyStr(Rec.Code, 1, MaxStrLen(Code1));
+ UserFieldMgt.Form_Userfield_Edit(6010313, 2, '', Code1, 0, 0, 0, 0, 0);
+ end;
+ }
+ }
+ }
+ }
+
+ trigger OnOpenPage()
+ var
+ SetupRec: Record "PVS General Setup";
+ begin
+ SetupRec.Get();
+
+ PUSH_USERFIELDS_1Visible := SetupRec."Enable User Fields Job 1";
+ PUSH_USERFIELDS_2Visible := SetupRec."Enable User Fields Job 2";
+ PUSH_USERFIELDS_3Visible := SetupRec."Enable User Fields Job 3";
+ end;
+
+ var
+ PUSH_USERFIELDS_1Visible: Boolean;
+ PUSH_USERFIELDS_2Visible: Boolean;
+ PUSH_USERFIELDS_3Visible: Boolean;
+}
diff --git a/Product Configuration Enhancement/Product Configuration Enhancement/src/Extensions/PTEProductMaterialsExt.TableExt.al b/Product Configuration Enhancement/Product Configuration Enhancement/src/Extensions/PTEProductMaterialsExt.TableExt.al
new file mode 100644
index 0000000..325ff36
--- /dev/null
+++ b/Product Configuration Enhancement/Product Configuration Enhancement/src/Extensions/PTEProductMaterialsExt.TableExt.al
@@ -0,0 +1,12 @@
+tableextension 75005 "PTE Product Materials Ext" extends "PVS Product Materials"
+{
+ fields
+ {
+ field(75010; "Job Item No."; Integer)
+ {
+ Caption = 'Job Item No.';
+ DataClassification = SystemMetadata;
+ TableRelation = "PTE Product Job Item"."Job Item No." where("Product Code" = field("Product Code"));
+ }
+ }
+}
diff --git a/Product Configuration Enhancement/Product Configuration Enhancement/src/ProductSetup/PTEProductJobItem.Table.al b/Product Configuration Enhancement/Product Configuration Enhancement/src/ProductSetup/PTEProductJobItem.Table.al
new file mode 100644
index 0000000..2bc008d
--- /dev/null
+++ b/Product Configuration Enhancement/Product Configuration Enhancement/src/ProductSetup/PTEProductJobItem.Table.al
@@ -0,0 +1,501 @@
+table 75000 "PTE Product Job Item"
+{
+ DataClassification = ToBeClassified;
+
+ fields
+ {
+ field(1; "Product Code"; Code[20])
+ {
+ Caption = 'Product Code';
+ DataClassification = SystemMetadata;
+ TableRelation = "PVS Product".Code;
+ ToolTip = 'Specifies the product code.';
+ }
+ field(2; "Job Item No."; Integer)
+ {
+ Caption = 'Job Item No.';
+ DataClassification = SystemMetadata;
+ ToolTip = 'Specifies the job item number.';
+ }
+ field(6010116; "Format Code"; Code[20])
+ {
+ DataClassification = CustomerContent;
+ Caption = 'Format Code';
+ ToolTip = 'Rather than using Format1 and Format2, a Format Code may be entered to give the size of the Paper/Substrate or Plate.';
+ TableRelation = "PVS Format Code" where(Type = filter(Miscellaneous | "Final format"));
+ ValidateTableRelation = false;
+
+ trigger OnValidate()
+ var
+ GeneralSetup: Record "PVS General Setup";
+ FormatCode: Record "PVS Format Code";
+ begin
+ if not FormatCode.Get(Rec."Format Code") then
+ exit;
+
+ if GeneralSetup.Get() then;
+
+ if GeneralSetup."Manual Grain Direction" then begin
+ Rec.Validate(Width, FormatCode."Width Centimeter");
+ Rec.Validate(Length, FormatCode."Length Centimeter");
+ end else
+ if GeneralSetup."Format Entry" = GeneralSetup."format entry"::"Depth x Width" then begin
+ Rec.Validate("Format1", FormatCode."Length Centimeter");
+ Rec.Validate("Format2", FormatCode."Width Centimeter");
+ end else begin
+ Rec.Validate("Format1", FormatCode."Width Centimeter");
+ Rec.Validate("Format2", FormatCode."Length Centimeter");
+ end;
+ end;
+ }
+ field(6010051; "Format1"; Decimal)
+ {
+ DataClassification = CustomerContent;
+ AutoFormatType = 0;
+ DecimalPlaces = 2 : 6;
+ BlankZero = true;
+ Caption = 'Format1';
+ ToolTip = 'Size of the current product (width or length depending on setup).';
+ }
+ field(6010052; "Format2"; Decimal) //widrg
+ {
+ DataClassification = CustomerContent;
+ AutoFormatType = 0;
+ DecimalPlaces = 2 : 6;
+ BlankZero = true;
+ Caption = 'Format2';
+ ToolTip = 'Size of the current product (width or length depending on setup).';
+ }
+ //Refactored to Length and Width fields only used for the format code validation, the Format1 and Format2 fields are used for the actual format code values.
+ field(6010130; Length; Decimal)
+ {
+ DataClassification = CustomerContent;
+ AutoFormatType = 0;
+ DecimalPlaces = 2 : 6;
+ Caption = 'Length';
+ ToolTip = 'Specifies the length used for format code validation.';
+ Description = 'PRINTVIS';
+ }
+ //Refactored to Length and Width fields only used for the format code validation, the Format1 and Format2 fields are used for the actual format code values.
+
+ field(6010131; Width; Decimal)
+ {
+ DataClassification = CustomerContent;
+ AutoFormatType = 0;
+ DecimalPlaces = 2 : 6;
+ Caption = 'Width';
+ ToolTip = 'Specifies the width used for format code validation.';
+ }
+
+ field(6010054; "Weight"; Decimal)
+ {
+ DataClassification = CustomerContent;
+ AutoFormatType = 0;
+ DecimalPlaces = 2 : 6;
+ BlankZero = true;
+ Caption = 'Weight';
+ ToolTip = 'Displays the weight of the Paper.';
+ }
+ field(6010073; "Imposition Code"; Code[20])
+ {
+ DataClassification = CustomerContent;
+ Caption = 'Imposition Code';
+ TableRelation = "PVS Imposition Code";
+ ToolTip = 'Use to link a specific Imposition Type to the Item that will be transferred to the new case if: 1.Field is empty in template, AND 2. Number of items in the length and width on imposition code must match length and width [leaves] of the template case, or the "Manual Pages On The Sheet" is set to True, by entering manual pages on sheet or impose 1/2.';
+
+ trigger OnValidate()
+ var
+ ImpositionRec: Record "PVS Imposition Code";
+ begin
+ if "Imposition Code" <> '' then begin
+ ImpositionRec.Get("Imposition Code");
+ Rec."Format1" := ImpositionRec."Format 1";
+ Rec."Format2" := ImpositionRec."Format 2";
+ end;
+ end;
+ }
+ field(6010105; "Paper No."; Code[20])
+ {
+ DataClassification = CustomerContent;
+ Caption = 'Paper No.';
+ ToolTip = 'Specifies the paper/substrate item number.';
+ TableRelation = Item;
+
+ trigger OnValidate()
+ var
+ ItemRec: Record Item;
+ begin
+ // Mirror PVS Job Sheet.Change_Paper: pull the paper weight from the item.
+ // Only overwrite when the item has a weight defined, to avoid clearing a
+ // manually entered value when the paper item has no weight.
+ if ItemRec.Get("Paper No.") then
+ if ItemRec."PVS Weight" <> 0 then
+ Validate(Weight, ItemRec."PVS Weight");
+ end;
+ }
+ field(6010115; "Paper Description"; Text[100])
+ {
+ CalcFormula = lookup(Item.Description where("No." = field("Paper No.")));
+ Caption = 'Paper Description';
+ ToolTip = 'Specifies the description of the paper/substrate item.';
+ Editable = false;
+ FieldClass = FlowField;
+ }
+ field(6010106; "Finishing"; Code[20])
+ {
+ DataClassification = CustomerContent;
+ Caption = 'Finishing';
+ ToolTip = 'If a certain Finishing Type code is to be added to estimations for this Job Item, enter it here.';
+ TableRelation = "PVS Finishing Types";
+ }
+ field(6010330; "Colors Front"; Integer)
+ {
+ DataClassification = CustomerContent;
+ Caption = 'Colors Front';
+ ToolTip = 'Displays number of colors on the front side.';
+ }
+ field(6010331; "Colors Back"; Integer)
+ {
+ DataClassification = CustomerContent;
+ Caption = 'Colors Back';
+ ToolTip = 'Displays number of colors on the back side.';
+ }
+ field(6010401; "Tool 1"; Code[20])
+ {
+ DataClassification = CustomerContent;
+ captionclass = 'PVS,FIELD6010388-1';
+ Caption = 'Tool 1';
+ ToolTip = 'Displays the first Tool for the current PrintVis Product, to properly load the required tools when doing re-runs of the product.';
+ TableRelation = "PVS Tool";
+ }
+ field(6010402; "Tool 2"; Code[20])
+ {
+ DataClassification = CustomerContent;
+ captionclass = 'PVS,FIELD6010388-2';
+ Caption = 'Tool 2';
+ ToolTip = 'Displays the second Tool for the current PrintVis Product, to properly load the required tools when doing re-runs of the product.';
+ TableRelation = "PVS Tool";
+ }
+ field(6010403; "Tool 3"; Code[20])
+ {
+ DataClassification = CustomerContent;
+ captionclass = 'PVS,FIELD6010388-3';
+ Caption = 'Tool 3';
+ ToolTip = 'Displays the third Tool for the current PrintVis Product, to properly load the required tools when doing re-runs of the product.';
+ TableRelation = "PVS Tool";
+ }
+ field(6010404; "Tool 4"; Code[20])
+ {
+ DataClassification = CustomerContent;
+ captionclass = 'PVS,FIELD6010388-4';
+ Caption = 'Tool 4';
+ ToolTip = 'Displays the fourth Tool for the current PrintVis Product, to properly load the required tools when doing re-runs of the product.';
+ TableRelation = "PVS Tool";
+ }
+ field(6010410; "Media Type"; Option)
+ {
+ DataClassification = CustomerContent;
+ Caption = 'Media Type';
+ ToolTip = 'Specifies the media type (e.g. Envelope).';
+ OptionCaption = ' ,Envelope,Envelope Plain,Envelope Window';
+ OptionMembers = " ",Envelope,"Envelope Plain","Envelope Window";
+ }
+ field(6010420; "List Of Units"; Code[20])
+ {
+ DataClassification = CustomerContent;
+ Caption = 'List Of Units';
+ ToolTip = 'Specifies the list of units code.';
+ TableRelation = "PVS Calculation Unit Setup".Code where("List Of Units" = const(true));
+
+ trigger OnValidate()
+ begin
+ ResolveConfiguration();
+ end;
+ }
+ field(6010421; "Configuration"; Code[20])
+ {
+ DataClassification = CustomerContent;
+ Caption = 'Configuration';
+ ToolTip = 'Specifies the Cost Center Configuration to use for the List Of Units. Resolved automatically when unambiguous, or selected manually when the machine/cost center has more than one matching configuration (mirrors the "PVS Calculation Configurations" selection shown on the PVS Job Item).';
+ }
+ field(6010411; "Envelope Window Shape Type"; Option)
+ {
+ DataClassification = CustomerContent;
+ Caption = 'Envelope Window Shape Type';
+ ToolTip = 'Specifies the shape type of the envelope window.';
+ OptionCaption = ' ,Rectangular,Round,Path';
+ OptionMembers = " ",Rectangular,Round,Path;
+ }
+ field(6010412; "Envelope Window Size X"; Decimal)
+ {
+ DataClassification = CustomerContent;
+ AutoFormatType = 0;
+ DecimalPlaces = 2 : 6;
+ BlankZero = true;
+ Caption = 'Envelope Window Size X';
+ ToolTip = 'Specifies the horizontal size of the envelope window.';
+ }
+ field(6010413; "Envelope Window Size Y"; Decimal)
+ {
+ DataClassification = CustomerContent;
+ AutoFormatType = 0;
+ DecimalPlaces = 2 : 6;
+ BlankZero = true;
+ Caption = 'Envelope Window Size Y';
+ ToolTip = 'Specifies the vertical size of the envelope window.';
+ }
+ field(6010414; "Opacity"; Option)
+ {
+ DataClassification = CustomerContent;
+ Caption = 'Opacity';
+ ToolTip = 'Specifies the opacity type of the product.';
+ OptionCaption = ' ,Opaque,Translucent,Transparent';
+ OptionMembers = " ",Opaque,Translucent,Transparent;
+ }
+ field(6010415; "Opacity Level"; Decimal)
+ {
+ DataClassification = CustomerContent;
+ AutoFormatType = 0;
+ BlankZero = true;
+ Caption = 'Opacity Level';
+ ToolTip = 'Specifies the opacity level of the product.';
+ DecimalPlaces = 0 : 0;
+ }
+ field(70; "Component Type"; Code[20])
+ {
+ DataClassification = CustomerContent;
+ Caption = 'Component Type';
+ ToolTip = 'Specifies the component type for this job item, defining how it is treated in the production calculation.';
+ TableRelation = "PVS Component Types";
+ }
+ field(71; "Component Type Description"; Text[50])
+ {
+ CalcFormula = lookup("PVS Component Types".Description where(Code = field("Component Type")));
+ Caption = 'Component Type Description';
+ ToolTip = 'Specifies the description of the component type.';
+ Editable = false;
+ FieldClass = FlowField;
+ }
+ field(23; "No. Of Pages"; Integer)
+ {
+ DataClassification = CustomerContent;
+ BlankZero = true;
+ Caption = 'No. of Pages';
+ ToolTip = 'Specifies the total number of pages for this job item. Must be an even number.';
+
+ trigger OnValidate()
+ var
+ unevenErr: Label 'No. of pages cannot be uneven';
+ begin
+ if ("No. Of Pages" mod 2) <> 0 then
+ Error(unevenErr);
+ end;
+ }
+ field(25; "Pages With Print"; Decimal)
+ {
+ DataClassification = CustomerContent;
+ BlankZero = true;
+ Caption = 'Pages with Print';
+ ToolTip = 'Specifies the number of pages that carry print for this job item.';
+ DecimalPlaces = 0 : 4;
+ }
+ }
+
+ keys
+ {
+ key(Key1; "Product Code", "Job Item No.")
+ {
+ Clustered = true;
+ }
+ }
+
+ fieldgroups
+ {
+ // Add changes to field groups here
+ }
+
+ trigger OnInsert()
+ var
+ ProductJobItem: Record "PTE Product Job Item";
+ ProductRec: Record "PVS Product";
+ begin
+ if "Job Item No." = 0 then begin
+ ProductJobItem.SetRange("Product Code", "Product Code");
+ if ProductJobItem.FindLast() then
+ "Job Item No." := ProductJobItem."Job Item No." + 1
+ else
+ "Job Item No." := 1;
+ end;
+ ProductRec.Get("Product Code");
+ if ProductRec."Format Code" <> '' then
+ Validate("Format Code", ProductRec."Format Code")
+ else begin
+
+ "Format1" := ProductRec."Format1";
+ "Format2" := ProductRec."Format2";
+ Length := ProductRec.Length;
+ Width := ProductRec.Width;
+ end;
+ Weight := ProductRec.Weight;
+ "Imposition Code" := ProductRec."Imposition Code";
+ "Paper No." := ProductRec."Paper No.";
+ Finishing := ProductRec.Finishing;
+ "Colors Front" := ProductRec."Colors Front";
+ "Colors Back" := ProductRec."Colors Back";
+ "Tool 1" := ProductRec."Tool 1";
+ "Tool 2" := ProductRec."Tool 2";
+ "Tool 3" := ProductRec."Tool 3";
+ "Tool 4" := ProductRec."Tool 4";
+ "Media Type" := ProductRec."Media Type";
+ "Envelope Window Shape Type" := ProductRec."Envelope Window Shape Type";
+ "Envelope Window Size X" := ProductRec."Envelope Window Size X";
+ "Envelope Window Size Y" := ProductRec."Envelope Window Size Y";
+ Opacity := ProductRec.Opacity;
+ "Opacity Level" := ProductRec."Opacity Level";
+
+ end;
+
+ trigger OnModify()
+ begin
+
+ end;
+
+ trigger OnDelete()
+ begin
+
+ end;
+
+ trigger OnRename()
+ begin
+
+ end;
+
+ ///
+ /// Resolves the Cost Center Configuration for the current "List Of Units" selection,
+ /// mirroring the base app's ambiguity handling on the real PVS Job Item (page
+ /// "PVS Calculation Configurations" / 6010357): candidate configurations are filtered
+ /// by this line's current Format1/Format2/Weight against each configuration's
+ /// Min/Max Printing Format and Weight ranges.
+ /// - 0 matches: "Configuration" is cleared (left for manual/runtime resolution).
+ /// - 1 match: assigned silently.
+ /// - 2+ matches: the user is prompted via "PVS Calculation Configurations" to pick one.
+ ///
+ local procedure ResolveConfiguration()
+ var
+ TempMatchingUnitSetup: Record "PVS Calculation Unit Setup" temporary;
+ Page_Configurations: Page "PVS Calculation Configurations";
+ Config_ARR: array[100] of Code[20];
+ Config_Max: Integer;
+ MatchCount: Integer;
+ begin
+ Rec.Configuration := '';
+
+ if "List Of Units" = '' then
+ exit;
+
+ MatchCount := GetMatchingConfigurations(TempMatchingUnitSetup);
+ case MatchCount of
+ 0:
+ exit;
+ 1:
+ begin
+ TempMatchingUnitSetup.FindFirst();
+ Rec.Configuration := TempMatchingUnitSetup.Configuration;
+ end;
+ else begin
+ Commit();
+ Clear(Page_Configurations);
+ Page_Configurations.Set_Filter_Units(TempMatchingUnitSetup);
+ if Page_Configurations.RunModal() = Action::OK then begin
+ Page_Configurations.Find_Marked(Config_Max, Config_ARR);
+ if Config_Max > 0 then
+ if TempMatchingUnitSetup.Get(TempMatchingUnitSetup.Type::"Price Unit", Config_ARR[1]) then
+ Rec.Configuration := TempMatchingUnitSetup.Configuration;
+ end;
+ end;
+ end;
+ end;
+
+ ///
+ /// Builds the set of Cost Center Configurations that are compatible with this line's
+ /// current Format1/Format2/Weight, under the Cost Center resolved from the "List Of
+ /// Units" calculation unit. Each match is represented by its concrete "PVS Calculation
+ /// Unit Setup" record, because the "PVS Calculation Configurations" picker page
+ /// (6010357) actually has SourceTable = "PVS Calculation Unit Setup" (confirmed from
+ /// the base app symbols) - not "PVS Cost Center Configuration". Also used by the sub
+ /// page to let the user manually reopen the picker and override the automatically
+ /// resolved Configuration.
+ ///
+ /// Filled with one Calculation Unit Setup record per matching Configuration.
+ /// The number of matching configurations found.
+ procedure GetMatchingConfigurations(var TempMatchingUnitSetup: Record "PVS Calculation Unit Setup" temporary): Integer
+ var
+ UnitSetup: Record "PVS Calculation Unit Setup";
+ CostCenterConfig: Record "PVS Cost Center Configuration";
+ CandidateUnitSetup: Record "PVS Calculation Unit Setup";
+ begin
+ TempMatchingUnitSetup.Reset();
+ TempMatchingUnitSetup.DeleteAll();
+
+ if "List Of Units" = '' then
+ exit(0);
+
+ if not UnitSetup.Get(UnitSetup.Type::"Price Unit", "List Of Units") then
+ exit(0);
+
+ if UnitSetup."Cost Center Code" = '' then
+ exit(0);
+
+ CostCenterConfig.SetRange("Cost Center Code", UnitSetup."Cost Center Code");
+ if CostCenterConfig.FindSet() then
+ repeat
+ if IsConfigurationMatch(CostCenterConfig) then begin
+ CandidateUnitSetup.Reset();
+ CandidateUnitSetup.SetRange(Type, CandidateUnitSetup.Type::"Price Unit");
+ CandidateUnitSetup.SetRange("Cost Center Code", CostCenterConfig."Cost Center Code");
+ CandidateUnitSetup.SetRange(Configuration, CostCenterConfig.Configuration);
+ CandidateUnitSetup.SetRange("List Of Units", false);
+ if CandidateUnitSetup.FindFirst() then
+ if not TempMatchingUnitSetup.Get(CandidateUnitSetup.Type, CandidateUnitSetup.Code) then begin
+ TempMatchingUnitSetup := CandidateUnitSetup;
+ TempMatchingUnitSetup.Insert();
+ end;
+ end;
+ until CostCenterConfig.Next() = 0;
+
+ exit(TempMatchingUnitSetup.Count());
+ end;
+
+ ///
+ /// Checks whether the given Cost Center Configuration's Min/Max Printing Format and
+ /// Weight ranges accommodate this line's current Format1/Format2/Weight values. A
+ /// blank/zero limit on the configuration, or a blank/zero value on this line, is
+ /// treated as "no constraint" for that dimension.
+ ///
+ local procedure IsConfigurationMatch(CostCenterConfig: Record "PVS Cost Center Configuration"): Boolean
+ begin
+ if (Format1 <> 0) then begin
+ if (CostCenterConfig."Max Printing Format Length" <> 0) and (Format1 > CostCenterConfig."Max Printing Format Length") then
+ exit(false);
+ if (CostCenterConfig."Min Print Format Length" <> 0) and (Format1 < CostCenterConfig."Min Print Format Length") then
+ exit(false);
+ end;
+
+ if (Format2 <> 0) then begin
+ if (CostCenterConfig."Max Printing Format Width" <> 0) and (Format2 > CostCenterConfig."Max Printing Format Width") then
+ exit(false);
+ if (CostCenterConfig."Min Print Format Width" <> 0) and (Format2 < CostCenterConfig."Min Print Format Width") then
+ exit(false);
+ end;
+
+ if (Weight <> 0) then begin
+ if (CostCenterConfig."Max Weight" <> 0) and (Weight > CostCenterConfig."Max Weight") then
+ exit(false);
+ if (CostCenterConfig."Min Weight" <> 0) and (Weight < CostCenterConfig."Min Weight") then
+ exit(false);
+ end;
+
+ exit(true);
+ end;
+
+}
diff --git a/Product Configuration Enhancement/Product Configuration Enhancement/src/ProductSetup/PTEProductJobItemColors.Page.al b/Product Configuration Enhancement/Product Configuration Enhancement/src/ProductSetup/PTEProductJobItemColors.Page.al
new file mode 100644
index 0000000..4b3be60
--- /dev/null
+++ b/Product Configuration Enhancement/Product Configuration Enhancement/src/ProductSetup/PTEProductJobItemColors.Page.al
@@ -0,0 +1,136 @@
+///
+/// Popup list page showing PVS Product Materials (inks / consumables) for a specific
+/// Product Job Item. Opened from the "PTE Product Job Item Sub" page via the
+/// "Colors / Materials" action or by drilling down on the Colors Front / Colors Back fields.
+///
+page 75002 "PTE Product Job Item Colors"
+{
+ PageType = List;
+ SourceTable = "PVS Product Materials";
+ Caption = 'Job Item Colors / Materials';
+ ApplicationArea = All;
+ InsertAllowed = true;
+ DeleteAllowed = true;
+
+ layout
+ {
+ area(Content)
+ {
+ repeater(Lines)
+ {
+ field("Item Quality Code"; Rec."Item Quality Code")
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies the quality of the ink or consumable item.';
+ }
+ field("Item No."; Rec."Item No.")
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies the ink or consumable item number.';
+
+ trigger OnLookup(var Text: Text): Boolean
+ var
+ Item: Record Item;
+ begin
+ // When an Item Quality Code is specified on the line, restrict the
+ // Item drilldown/lookup to items that share the same quality code.
+ if Rec."Item Quality Code" <> '' then
+ Item.SetRange("PVS Item Quality Code", Rec."Item Quality Code");
+
+ if Page.RunModal(0, Item) = Action::LookupOK then begin
+ Text := Item."No.";
+ exit(true);
+ end;
+ exit(false);
+ end;
+ }
+ field(Text; Rec.Text)
+ {
+ ApplicationArea = All;
+ ToolTip = 'Description transferred from the selected item, may be overwritten.';
+ }
+ field("Text 2"; Rec."Text 2")
+ {
+ ApplicationArea = All;
+ ToolTip = 'Secondary description transferred from the selected item, may be overwritten.';
+ }
+ field("F/B"; Rec."F/B")
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies whether the color is printed on the Front, Back, or Both sides.';
+ }
+ field(Type; Rec.Type)
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies the color type: Base Color, Spot Color, or Other.';
+ }
+ field("Coverage Pct."; Rec."Coverage Pct.")
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies the percentage of the sheet area covered with this ink at 100% coverage.';
+ }
+ field("Area Per Weight"; Rec."Area Per Weight")
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies how much area is covered with one unit of ink at 100% coverage.';
+ }
+ field("No. Of Washups"; Rec."No. Of Washups")
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies the number of color changes and washups to count for this sheet.';
+ }
+ field("Cliche No."; Rec."Cliche No.")
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies the cliche item number for flexo production.';
+ Visible = false;
+ }
+ field("Plate No."; Rec."Plate No.")
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies the printing plate item number.';
+ Visible = false;
+ }
+ field("Job Item No."; Rec."Job Item No.")
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies which Product Job Item this material line belongs to.';
+ }
+ field("Product Code"; Rec."Product Code")
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies the product code.';
+ Visible = false;
+ }
+ field("Entry No."; Rec."Entry No.")
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies the entry number.';
+ Visible = false;
+ }
+ }
+ }
+ }
+
+ trigger OnNewRecord(BelowxRec: Boolean)
+ var
+ ProductMaterials: Record "PVS Product Materials";
+ FilterProductCode: Code[20];
+ FilterJobItemNo: Integer;
+ begin
+ // Inherit the filtered Product Code and Job Item No. so every new line
+ // is automatically linked to the correct product and job item.
+ FilterProductCode := CopyStr(Rec.GetFilter("Product Code"), 1, MaxStrLen(Rec."Product Code"));
+ Evaluate(FilterJobItemNo, Rec.GetFilter("Job Item No."));
+
+ Rec."Product Code" := FilterProductCode;
+ Rec."Job Item No." := FilterJobItemNo;
+
+ // Auto-increment Entry No. within this product.
+ ProductMaterials.SetRange("Product Code", FilterProductCode);
+ if ProductMaterials.FindLast() then
+ Rec."Entry No." := ProductMaterials."Entry No." + 1
+ else
+ Rec."Entry No." := 1;
+ end;
+}
diff --git a/Product Configuration Enhancement/Product Configuration Enhancement/src/ProductSetup/PTEProductJobItemSub.Page.al b/Product Configuration Enhancement/Product Configuration Enhancement/src/ProductSetup/PTEProductJobItemSub.Page.al
new file mode 100644
index 0000000..48a22d1
--- /dev/null
+++ b/Product Configuration Enhancement/Product Configuration Enhancement/src/ProductSetup/PTEProductJobItemSub.Page.al
@@ -0,0 +1,275 @@
+page 75001 "PTE Product Job Item Sub"
+{
+ PageType = ListPart;
+ SourceTable = "PTE Product Job Item";
+ Caption = 'Product Job Items';
+ InsertAllowed = false;
+ DeleteAllowed = true;
+ DelayedInsert = true;
+ AutoSplitKey = true;
+ SourceTableView = sorting("Product Code", "Job Item No.");
+ ApplicationArea = All;
+
+ layout
+ {
+ area(Content)
+ {
+ repeater(General)
+ {
+ field("Product Code"; Rec."Product Code")
+ {
+
+ Visible = false;
+ }
+ field("Job Item No."; Rec."Job Item No.")
+ {
+
+ }
+ field("Component Type"; Rec."Component Type")
+ {
+
+ }
+ field("Component Type Description"; Rec."Component Type Description")
+ {
+
+ }
+ field("No. Of Pages"; Rec."No. Of Pages")
+ {
+
+ }
+ field("Pages With Print"; Rec."Pages With Print")
+ {
+
+ }
+ field("Format Code"; Rec."Format Code")
+ {
+
+ }
+ field("Format1"; Rec."Format1")
+ {
+
+ }
+ field("Format2"; Rec."Format2")
+ {
+
+ }
+ field(Weight; Rec.Weight)
+ {
+
+ }
+ field("Imposition Code"; Rec."Imposition Code")
+ {
+
+ }
+ field("Paper No."; Rec."Paper No.")
+ {
+
+ }
+ field("Paper Description"; Rec."Paper Description")
+ {
+
+ }
+ field(Finishing; Rec.Finishing)
+ {
+
+ }
+ field("Colors Front"; Rec."Colors Front")
+ {
+
+ trigger OnDrillDown()
+ begin
+ OpenColorsPage();
+ end;
+ }
+ field("Colors Back"; Rec."Colors Back")
+ {
+
+ trigger OnDrillDown()
+ begin
+ OpenColorsPage();
+ end;
+ }
+ field("Media Type"; Rec."Media Type")
+ {
+
+ }
+ field("List Of Units"; Rec."List Of Units")
+ {
+
+
+ trigger OnLookup(var Text: Text): Boolean
+ var
+ ProductRec: Record "PVS Product";
+ Temp_CalcUnitSetup: Record "PVS Calculation Unit Setup" temporary;
+ PageMgt: Codeunit "PVS Page Management";
+ ProductGroupCode: Code[20];
+ begin
+ if ProductRec.Get(Rec."Product Code") then
+ ProductGroupCode := ProductRec."Product Group Code";
+
+ PageMgt.Get_ListOfUnitTMP(ProductGroupCode, false, Temp_CalcUnitSetup);
+
+ Commit();
+ if Page.RunModal(Page::"PVS Calculation List of Units", Temp_CalcUnitSetup) = Action::LookupOK then begin
+ Rec.Validate("List Of Units", Temp_CalcUnitSetup.Code);
+ Text := Temp_CalcUnitSetup.Code;
+ exit(true);
+ end;
+ end;
+ }
+ field("Configuration"; Rec."Configuration")
+ {
+ ToolTip = 'Specifies the Cost Center Configuration resolved for the List Of Units. Automatically resolved when unambiguous; drill down to pick manually when the machine has more than one matching configuration.';
+
+ trigger OnLookup(var Text: Text): Boolean
+ var
+ TempMatchingUnitSetup: Record "PVS Calculation Unit Setup" temporary;
+ Page_Configurations: Page "PVS Calculation Configurations";
+ Config_ARR: array[100] of Code[20];
+ Config_Max: Integer;
+ begin
+ if Rec."List Of Units" = '' then
+ exit(false);
+
+ if Rec.GetMatchingConfigurations(TempMatchingUnitSetup) = 0 then
+ exit(false);
+
+ Commit();
+ Clear(Page_Configurations);
+ Page_Configurations.Set_Filter_Units(TempMatchingUnitSetup);
+ if Page_Configurations.RunModal() = Action::OK then begin
+ Page_Configurations.Find_Marked(Config_Max, Config_ARR);
+ if Config_Max = 0 then
+ exit(false);
+
+ if not TempMatchingUnitSetup.Get(TempMatchingUnitSetup.Type::"Price Unit", Config_ARR[1]) then
+ exit(false);
+
+ Rec.Validate("Configuration", TempMatchingUnitSetup.Configuration);
+ Text := TempMatchingUnitSetup.Configuration;
+ exit(true);
+ end;
+ end;
+ }
+ field("Envelope Window Shape Type"; Rec."Envelope Window Shape Type")
+ {
+
+ }
+ field("Envelope Window Size X"; Rec."Envelope Window Size X")
+ {
+
+ }
+ field("Envelope Window Size Y"; Rec."Envelope Window Size Y")
+ {
+
+ }
+ field(Opacity; Rec.Opacity)
+ {
+
+ }
+ field("Opacity Level"; Rec."Opacity Level")
+ {
+
+ }
+ field("Tool1"; Rec."Tool 1")
+ {
+
+ }
+ field("Tool2"; Rec."Tool 2")
+ {
+
+ }
+ field("Tool3"; Rec."Tool 3")
+ {
+
+ }
+ field("Tool4"; Rec."Tool 4")
+ {
+
+ }
+ }
+ }
+
+ }
+
+ actions
+ {
+ area(Processing)
+ {
+ action(NewJobItem)
+ {
+
+ Caption = 'New Job Item';
+ ToolTip = 'Creates a new product job item line for this product.';
+ Image = NewRow;
+
+ trigger OnAction()
+ var
+ ProductJobItem: Record "PTE Product Job Item";
+ begin
+ ProductJobItem.Init();
+ ProductJobItem."Product Code" := Rec."Product Code";
+ ProductJobItem.Insert(true);
+ CurrPage.Update(false);
+ end;
+ }
+ action(CopyJobItem)
+ {
+
+ Caption = 'Copy Job Item';
+ ToolTip = 'Creates a copy of the current product job item line.';
+ Image = Copy;
+ Enabled = HasJobItemLines;
+ trigger OnAction()
+ var
+ NewProductJobItem: Record "PTE Product Job Item";
+ begin
+ Rec.TestField("Product Code");
+ NewProductJobItem := Rec;
+ NewProductJobItem."Job Item No." := 0;
+ NewProductJobItem.Insert(true);
+ CurrPage.Update(false);
+ end;
+ }
+ action(ViewColors)
+ {
+ ApplicationArea = All;
+ Caption = 'Colors / Materials';
+ ToolTip = 'Opens the colors and materials lines for the selected job item.';
+ Image = ItemLedger;
+ trigger OnAction()
+ begin
+ OpenColorsPage();
+ end;
+ }
+ }
+
+ }
+
+ trigger OnAfterGetCurrRecord()
+ var
+ ProductJobItem: Record "PTE Product Job Item";
+ begin
+ ProductJobItem.SetRange("Product Code", Rec."Product Code");
+ HasJobItemLines := not ProductJobItem.IsEmpty();
+ end;
+
+ var
+ HasJobItemLines: Boolean;
+
+ procedure GetCurrentJobItemNo(): Integer
+ begin
+ exit(Rec."Job Item No.");
+ end;
+
+ local procedure OpenColorsPage()
+ var
+ ProductMaterials: Record "PVS Product Materials";
+ begin
+ Rec.TestField("Product Code");
+ ProductMaterials.SetRange("Product Code", Rec."Product Code");
+ if Rec."Job Item No." <> 0 then
+ ProductMaterials.SetRange("Job Item No.", Rec."Job Item No.");
+ Page.RunModal(Page::"PTE Product Job Item Colors", ProductMaterials);
+ end;
+}
diff --git a/Product Configuration Enhancement/Product Configuration Enhancement/src/ProductSetup/PTEProductTemplateMgt.Codeunit.al b/Product Configuration Enhancement/Product Configuration Enhancement/src/ProductSetup/PTEProductTemplateMgt.Codeunit.al
new file mode 100644
index 0000000..62ac876
--- /dev/null
+++ b/Product Configuration Enhancement/Product Configuration Enhancement/src/ProductSetup/PTEProductTemplateMgt.Codeunit.al
@@ -0,0 +1,360 @@
+codeunit 75004 "PTE Product Template Mgt"
+{
+ trigger OnRun()
+ begin
+ end;
+
+ ///
+ /// When a product is validated on a PVS Job, apply the product header fields
+ /// (Format Code / Colors), transfer the product user fields to the job, and sync
+ /// all matching PVS Job Item records with the field values from the corresponding
+ /// PTE Product Job Item template lines.
+ /// Matching is by Job Item No. Only primary lines (Entry No. = 1) are targeted.
+ /// Extra existing PVS Job Items without a template match are left untouched.
+ ///
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"PVS Product Management", OnAfterValidateProductPVSJob, '', false, false)]
+ local procedure OnAfterValidateProductPVSJob(var Job: Record "PVS Job"; var xJob: Record "PVS Job")
+ begin
+ // 1. Copy product header fields (Format Code / Colors) to the job header.
+ ApplyProductHeaderFields(Job);
+
+ // 2. Transfer the product user field values to the job.
+ ApplyProductUserFields(Job);
+
+ // 3. Sync template lines to the job items.
+ SyncTemplateLinesToJobItems(Job);
+ end;
+
+ ///
+ /// Copies the userfield values assigned to the product into a job
+ ///
+ local procedure ApplyProductUserFields(var Job: Record "PVS Job")
+ var
+ UserfieldValueRec: Record "PVS Userfield Field Value";
+ UserfieldMgt: Codeunit "PVS Userfield Management";
+ UniqueTables: Dictionary of [Integer, Boolean];
+ ProductCode: Code[20];
+ begin
+ ProductCode := CopyStr(Job."Product Code", 1, MaxStrLen(ProductCode));
+ UserfieldValueRec.SetRange(Code1, ProductCode);
+ if UserfieldValueRec.Findset() then
+ repeat
+ if not UniqueTables.ContainsKey(UserfieldValueRec."Table ID") then begin
+ UniqueTables.Add(UserfieldValueRec."Table ID", true);
+ UserfieldMgt.Copy_Record_UserFields(UserfieldValueRec."Table ID", '', ProductCode, 0, 0, 0, 0, 0, '', Job.ID, Job.Job, Job.Version, 0, 0, false);
+ end;
+ until UserfieldValueRec.Next() = 0;
+ end;
+
+ ///
+ /// Copies the product header fields (Format Code, Colors Front, Colors Back) to the
+ /// PVS Job when a product is applied. Formerly executed inside
+ /// PVS Product Management.ValidateProductPVSJob; moved here so the base app remains
+ /// unchanged. Persisted with Modify(false) so the values survive the subsequent
+ /// Job.Get refresh performed by SyncTemplateLinesToJobItems.
+ ///
+ local procedure ApplyProductHeaderFields(var Job: Record "PVS Job")
+ var
+ Product: Record "PVS Product";
+ Changed: Boolean;
+ begin
+ if Job."Product Code" = '' then
+ exit;
+ if not Product.Get(Job."Product Code") then
+ exit;
+
+ if (Product."Format Code" <> '') and (Job."Format Code" <> Product."Format Code") then begin
+ Job.Validate("Format Code", Product."Format Code");
+ Changed := true;
+ end;
+
+ if (Product."Colors Front" <> 0) or (Product."Colors Back" <> 0) then begin
+ if Job."Colors Front" <> Product."Colors Front" then begin
+ Job.Validate("Colors Front", Product."Colors Front");
+ Changed := true;
+ end;
+ if Job."Colors Back" <> Product."Colors Back" then begin
+ Job.Validate("Colors Back", Product."Colors Back");
+ Changed := true;
+ end;
+ end;
+
+ if Changed then
+ Job.Modify(false);
+
+ OnAfterApplyProductHeaderFields(Job);
+ end;
+
+ local procedure SyncTemplateLinesToJobItems(var Job: Record "PVS Job")
+ var
+ TmplLine: Record "PTE Product Job Item";
+ JobItem: Record "PVS Job Item";
+ JobSheet: Record "PVS Job Sheet";
+ PageMgt: Codeunit "PVS Page Management";
+ SheetMgt: Codeunit "PVS Sheet Management";
+ SingleInstance: Codeunit "PVS SingleInstance";
+ UnitCode: Code[20];
+ Changed: Boolean;
+ SheetChanged: Boolean;
+ PrevGUINotAllowed: Boolean;
+ begin
+ if Job."Product Code" = '' then
+ exit;
+
+ TmplLine.SetRange("Product Code", Job."Product Code");
+ TmplLine.SetCurrentKey("Product Code", "Job Item No.");
+ if not TmplLine.FindSet() then
+ exit;
+
+ // Suppress any base-app popups (e.g. "PVS Calculation Configurations" ambiguity
+ // prompt, or List Of Units surcharge prompts) while template lines are applied
+ // automatically. The base app is left to auto-resolve a default silently; there
+ // is no writable Configuration field on "PVS Job Sheet" to force a specific value.
+ PrevGUINotAllowed := SingleInstance.Get_GUINOTALLOWED();
+ SingleInstance.Set_GUINOTALLOWED(true);
+
+ repeat
+ // Locate primary PVS Job Item for this job + template line number
+ JobItem.SetRange(ID, Job.ID);
+ JobItem.SetRange(Job, Job.Job);
+ JobItem.SetRange(Version, Job.Version);
+ JobItem.SetRange("Job Item No.", TmplLine."Job Item No.");
+ JobItem.SetRange("Entry No.", 1);
+ if not JobItem.FindFirst() then begin
+ // Job Item does not exist: create a new Sheet + Job Item via PVS Sheet Management.
+ // Event_Create_Sheet takes var in_Rec and calls FindLast() before returning,
+ // so JobItem is already positioned on the newly created record afterwards.
+ JobItem.Reset();
+ JobItem.ID := Job.ID;
+ JobItem.Job := Job.Job;
+ JobItem.Version := Job.Version;
+ SheetMgt.Event_Create_Sheet(JobItem, false);
+
+ // If the record was not repositioned (creation failed), skip this line.
+ if JobItem."Entry No." = 0 then
+ continue;
+ end;
+
+ Clear(Changed);
+
+ // --- 1. Component Type ---
+ // Apply first: sets description only, no size or calc cascades.
+ if (TmplLine."Component Type" <> '') and (JobItem."Component Type" <> TmplLine."Component Type") then begin
+ JobItem.Validate("Component Type", TmplLine."Component Type");
+ Changed := true;
+ end;
+
+ // --- 2. No. Of Pages ---
+ // Standalone even-number validation. Must precede Pages With Print.
+ if JobItem."No. Of Pages" <> TmplLine."No. Of Pages" then
+ if (TmplLine."No. Of Pages" <> 0) and ((TmplLine."No. Of Pages" mod 2) = 0) then begin
+ JobItem.Validate("No. Of Pages", TmplLine."No. Of Pages");
+ Changed := true;
+ end;
+
+ // --- 3. Pages With Print ---
+ // Logically depends on No. Of Pages; validate after.
+ if (TmplLine."Pages With Print" <> 0) and (JobItem."Pages With Print" <> TmplLine."Pages With Print") then begin
+ JobItem.Validate("Pages With Print", TmplLine."Pages With Print");
+ Changed := true;
+ end;
+
+ // --- 4-5. Size: Length and Width ---
+ // Resolved from template using priority: Imposition wins > Format Code > direct Length/Width.
+ // Length validated before Width to trigger imposition recalc only once.
+ ApplyTemplateSizeToJobItem(TmplLine, JobItem, Changed);
+
+ // --- 6-7. Colors ---
+ // Standalone; no cascades between front and back.
+ if (TmplLine."Colors Front" <> 0) and (JobItem."Colors Front" <> TmplLine."Colors Front") then begin
+ JobItem.Validate("Colors Front", TmplLine."Colors Front");
+ Changed := true;
+ end;
+ if (TmplLine."Colors Back" <> 0) and (JobItem."Colors Back" <> TmplLine."Colors Back") then begin
+ JobItem.Validate("Colors Back", TmplLine."Colors Back");
+ Changed := true;
+ end;
+
+ // --- 8. Paper ---
+ // The Job Item's "Paper Item No." is a non-editable FlowField into the Sheet,
+ // so Paper (like Weight) is applied at sheet level below - see step 19.
+
+ // --- 9-12. Tools ---
+ // All standalone; no cascades between tool slots.
+ if (TmplLine."Tool 1" <> '') and (JobItem.Tool <> TmplLine."Tool 1") then begin
+ JobItem.Validate(Tool, TmplLine."Tool 1");
+ Changed := true;
+ end;
+ if (TmplLine."Tool 2" <> '') and (JobItem."Tool 2" <> TmplLine."Tool 2") then begin
+ JobItem.Validate("Tool 2", TmplLine."Tool 2");
+ Changed := true;
+ end;
+ if (TmplLine."Tool 3" <> '') and (JobItem."Tool 3" <> TmplLine."Tool 3") then begin
+ JobItem.Validate("Tool 3", TmplLine."Tool 3");
+ Changed := true;
+ end;
+ if (TmplLine."Tool 4" <> '') and (JobItem."Tool 4" <> TmplLine."Tool 4") then begin
+ JobItem.Validate("Tool 4", TmplLine."Tool 4");
+ Changed := true;
+ end;
+
+ // --- 13-18. Media / Envelope / Opacity ---
+ // All standalone; grouped by functional area to minimize record traffic.
+ if (TmplLine."Media Type" <> TmplLine."Media Type"::" ") and (JobItem."Media Type" <> TmplLine."Media Type") then begin
+ JobItem.Validate("Media Type", TmplLine."Media Type");
+ Changed := true;
+ end;
+ if (TmplLine."Envelope Window Shape Type" <> TmplLine."Envelope Window Shape Type"::" ") and (JobItem."Envelope Window Shape Type" <> TmplLine."Envelope Window Shape Type") then begin
+ JobItem.Validate("Envelope Window Shape Type", TmplLine."Envelope Window Shape Type");
+ Changed := true;
+ end;
+ if (TmplLine."Envelope Window Size X" <> 0) and (JobItem."Envelope Window Size X" <> TmplLine."Envelope Window Size X") then begin
+ JobItem.Validate("Envelope Window Size X", TmplLine."Envelope Window Size X");
+ Changed := true;
+ end;
+ if (TmplLine."Envelope Window Size Y" <> 0) and (JobItem."Envelope Window Size Y" <> TmplLine."Envelope Window Size Y") then begin
+ JobItem.Validate("Envelope Window Size Y", TmplLine."Envelope Window Size Y");
+ Changed := true;
+ end;
+ if (TmplLine.Opacity <> TmplLine.Opacity::" ") and (JobItem.Opacity <> TmplLine.Opacity) then begin
+ JobItem.Validate(Opacity, TmplLine.Opacity);
+ Changed := true;
+ end;
+ if (TmplLine."Opacity Level" <> 0) and (JobItem."Opacity Level" <> TmplLine."Opacity Level") then begin
+ JobItem.Validate("Opacity Level", TmplLine."Opacity Level");
+ Changed := true;
+ end;
+
+ // Persist all field changes in a single Modify before List Of Units.
+ // Use Modify(false) to avoid triggering PVS Job Item's OnModify cascade,
+ // which would call Modify() on the parent PVS Job record and cause an
+ // optimistic concurrency conflict when the page tries to save Product Code.
+ // Field-level triggers have already run via Validate() above.
+ if Changed then
+ JobItem.Modify(false);
+
+ // --- 19. List Of Units ---
+ // Assigns the Controlling (Sheet) Unit and, through it, resolves the Cost
+ // Center Configuration for the sheet. Must run BEFORE Finishing/Paper/Weight:
+ // the base app's "PVS Calculation Configurations" selection page is prompted
+ // when Finishing/Paper/Weight are validated on a sheet whose Configuration has
+ // not been resolved yet. Manually picking List Of Units first (as done on the
+ // Product Job Item sub page / base Job Items list) avoids that prompt, so the
+ // same order is used here. Must be after Modify so Job_Item_Input_Unit reads
+ // the persisted state. Skipped silently when no Sheet exists yet (Sheet ID = 0).
+ if TmplLine."List Of Units" <> '' then
+ if JobItem."Sheet ID" <> 0 then begin
+ UnitCode := TmplLine."List Of Units";
+ PageMgt.Job_Item_Input_Unit(JobItem, UnitCode);
+ end;
+
+ // --- 20. Finishing / Paper / Weight ---
+ // All three are stored on the PVS Job Sheet (the Job Item's "Paper Item No." and
+ // "Paper Weight" are FlowField lookups into it). Applied at sheet level, after
+ // List Of Units above, so the sheet's Configuration is already resolved and the
+ // "PVS Calculation Configurations" selection page does not get triggered. Finishing
+ // is applied before Paper/Weight since it is the more likely input to that
+ // configuration resolution. Paper is applied before Weight because changing paper
+ // (Change_Paper) can reset the default weight, which we then override.
+ // Skipped silently when no Sheet exists yet (Sheet ID = 0).
+ if JobItem."Sheet ID" <> 0 then
+ if JobSheet.Get(JobItem."Sheet ID") then begin
+ Clear(SheetChanged);
+ if (TmplLine.Finishing <> '') and (JobSheet.Finishing <> TmplLine.Finishing) then begin
+ JobSheet.Validate(Finishing, TmplLine.Finishing);
+ SheetChanged := true;
+ end;
+ if (TmplLine."Paper No." <> '') and (JobSheet."Paper Item No." <> TmplLine."Paper No.") then begin
+ JobSheet.Validate("Paper Item No.", TmplLine."Paper No.");
+ SheetChanged := true;
+ end;
+ if (TmplLine.Weight <> 0) and (JobSheet.Weight <> TmplLine.Weight) then begin
+ JobSheet.Validate(Weight, TmplLine.Weight);
+ SheetChanged := true;
+ end;
+ if SheetChanged then
+ JobSheet.Modify(true);
+ end;
+
+ until TmplLine.Next() = 0;
+
+ SingleInstance.Set_GUINOTALLOWED(PrevGUINotAllowed);
+
+ // Refresh the Job record from the database. SheetMgt.Event_Create_Sheet and
+ // field Validate cascades may have internally called Modify() on the PVS Job
+ // record, bumping its DB timestamp. Because Job is passed as var, re-reading it
+ // here updates the caller's (PVS Product Management) copy so its subsequent
+ // Modify() uses the current timestamp and avoids the optimistic concurrency
+ // conflict on the case card page.
+ if Job.Get(Job.ID, Job.Job, Job.Version) then;
+ end;
+
+ ///
+ /// Resolves the final Length and Width for a PVS Job Item from the template line.
+ /// Priority: Imposition Code (wins) > Format Code > direct Length/Width values.
+ /// Validates Length before Width to trigger imposition recalculation only once.
+ ///
+ local procedure ApplyTemplateSizeToJobItem(TmplLine: Record "PTE Product Job Item"; var JobItem: Record "PVS Job Item"; var Changed: Boolean)
+ var
+ GeneralSetup: Record "PVS General Setup";
+ ImpositionRec: Record "PVS Imposition Code";
+ FormatCode: Record "PVS Format Code";
+ FinalLength: Decimal;
+ FinalWidth: Decimal;
+ begin
+ if not GeneralSetup.Get() then
+ GeneralSetup.Init();
+
+ FinalLength := 0;
+ FinalWidth := 0;
+
+ // Imposition Code wins: read Format 1 / Format 2 from imposition record
+ if TmplLine."Imposition Code" <> '' then begin
+ if ImpositionRec.Get(TmplLine."Imposition Code") then
+ if GeneralSetup."Format Entry" = GeneralSetup."format entry"::"Depth x Width" then begin
+ // Depth x Width: Format 1 = depth/length, Format 2 = width
+ FinalLength := ImpositionRec."Format 1";
+ FinalWidth := ImpositionRec."Format 2";
+ end else begin
+ // Width x Length (default): Format 1 = width, Format 2 = length
+ FinalWidth := ImpositionRec."Format 1";
+ FinalLength := ImpositionRec."Format 2";
+ end;
+ end else
+ if TmplLine."Format Code" <> '' then begin
+ // Format Code: resolve centimeters via setup
+ if FormatCode.Get(TmplLine."Format Code") then
+ if GeneralSetup."Manual Grain Direction" then begin
+ FinalWidth := FormatCode."Width Centimeter";
+ FinalLength := FormatCode."Length Centimeter";
+ end else
+ if GeneralSetup."Format Entry" = GeneralSetup."format entry"::"Depth x Width" then begin
+ FinalLength := FormatCode."Length Centimeter";
+ FinalWidth := FormatCode."Width Centimeter";
+ end else begin
+ FinalWidth := FormatCode."Width Centimeter";
+ FinalLength := FormatCode."Length Centimeter";
+ end;
+ end else begin
+ // No format driver: use template Length/Width directly
+ FinalLength := TmplLine.Length;
+ FinalWidth := TmplLine.Width;
+ end;
+
+ // Apply Length first (triggers imposition recalculation internally once),
+ // then Width; skip zero values to avoid clearing existing data.
+ if (FinalLength <> 0) and (JobItem.Length <> FinalLength) then begin
+ JobItem.Validate(Length, FinalLength);
+ Changed := true;
+ end;
+ if (FinalWidth <> 0) and (JobItem.Width <> FinalWidth) then begin
+ JobItem.Validate(Width, FinalWidth);
+ Changed := true;
+ end;
+ end;
+
+ [IntegrationEvent(false, false)]
+ local procedure OnAfterApplyProductHeaderFields(var Job: Record "PVS Job")
+ begin
+ end;
+}