From 2c8d3e38df0004e30ea6208d17cf84c1e68989bb Mon Sep 17 00:00:00 2001
From: lihongji <3117313295@qq.com>
Date: 星期日, 13 十月 2024 19:24:34 +0800
Subject: [PATCH] 下线计划优化

---
 _Main/UI/MacroPlannerWebApp/Component_FormNewOfflinePlan/Component_matrixeditorContextMenu229.def                    |   24 ++
 _Main/UI/MacroPlannerWebApp/Component_FormNewOfflinePlan/Component_pHeader.def                                       |   60 +++++
 _Main/BL/Type_NewOfflinePlanCell/StaticMethod_RefreshOfflinePlan.qbl                                                 |  115 ++++++++--
 _Main/BL/Type_NewOfflinePlanCell/StaticMethod_SaveAsDraft.qbl                                                        |   16 +
 _Main/Sys/Repr/Global/NewOfflinePlanCell.qrp                                                                         |   22 ++
 _Main/BL/Type_NewOfflinePlanCell/StaticMethod_OldDownload.qbl                                                        |   49 ++++
 _Main/BL/Type_NewOfflinePlanCell/Method_FindProductionLineAndType.qbl                                                |    9 
 _Main/BL/Type_NewOfflinePlanCell/StaticMethod_Download.qbl                                                           |   16 +
 _Main/UI/MacroPlannerWebApp/Component_FormNewOfflinePlan/Response_pHeader_bRestore_OnClick#141.def                   |   27 ++
 _Main/UI/MacroPlannerWebApp/Component_FormOfflinePlan/Component_matrixeditorContextMenu204.def                       |   24 ++
 _Main/UI/MacroPlannerWebApp/Component_FormNewOfflinePlan/Response_pHeader_bImport_OnClick#568.def                    |   45 ++++
 _Main/UI/MacroPlannerWebApp/Component_FormNewOfflinePlan/Response_matrixeditorContextMenu229_mShowOrHide_OnClick.def |   16 +
 _Main/BL/Type_NewOfflinePlanCell/StaticMethod_CreateOldOfflinePlanData.qbl                                           |   79 +++++++
 _Main/BL/Relations/Relation_NewOfflinePlanTable_NewOfflinePlanColumn_NewOfflinePlanColumn_NewOf.qbl                  |   13 +
 _Main/BL/Type_NewOfflinePlanCell/StaticMethod_Import.qbl                                                             |   61 +++++
 _Main/UI/MacroPlannerWebApp/Component_FormNewOfflinePlan/Component_MatrixEditor583.def                               |    1 
 _Main/UI/MacroPlannerWebApp/Component_FormNewOfflinePlan/Response_pHeader_bSaveAsDraft_OnClick#870.def               |   23 ++
 _Main/UI/MacroPlannerWebApp/Component_FormOfflinePlan/Response_matrixeditorContextMenu204_mCreateData_OnClick.def    |   25 ++
 _Main/UI/MacroPlannerWebApp/Component_FormNewOfflinePlan/Response_pHeader_bDownload_OnClick#870.def                  |   22 ++
 19 files changed, 622 insertions(+), 25 deletions(-)

diff --git a/_Main/BL/Relations/Relation_NewOfflinePlanTable_NewOfflinePlanColumn_NewOfflinePlanColumn_NewOf.qbl b/_Main/BL/Relations/Relation_NewOfflinePlanTable_NewOfflinePlanColumn_NewOfflinePlanColumn_NewOf.qbl
index c0d44b2..c5d4e76 100644
--- a/_Main/BL/Relations/Relation_NewOfflinePlanTable_NewOfflinePlanColumn_NewOfflinePlanColumn_NewOf.qbl
+++ b/_Main/BL/Relations/Relation_NewOfflinePlanTable_NewOfflinePlanColumn_NewOfflinePlanColumn_NewOf.qbl
@@ -3,8 +3,19 @@
 Relation NewOfflinePlanTable_NewOfflinePlanColumn_NewOfflinePlanColumn_NewOfflinePlanTable
 {
   #keys: '1[413988.0.1603460118]'
-  DefaultRelationStrategy
+  DeclarativeSequenceRelationStrategy
   {
+    #keys: '13[0.0.0][413988.0.1648701086][413988.0.1648701080][413988.0.1648701087][413988.0.1648701081][413988.0.1648701088][413988.0.1648701082][413988.0.1648701089][413988.0.1648701083][413988.0.1648701090][413988.0.1648701084][413988.0.1648701091][413988.0.1648701085]'
+    SequenceElementSuffix: 'NOPColumn'
+    SequenceSuffix: 'NOPColumn'
+    SortAttributes:
+    [
+      DeclarativeSequenceRelationSortAttribute
+      {
+        #keys: '1[413988.0.1648701093]'
+        Attribute: 'StartDate'
+      }
+    ]
   }
   RelationSide.LeftSide NewOfflinePlanColumn
   {
diff --git a/_Main/BL/Type_NewOfflinePlanCell/Method_FindProductionLineAndType.qbl b/_Main/BL/Type_NewOfflinePlanCell/Method_FindProductionLineAndType.qbl
new file mode 100644
index 0000000..582b336
--- /dev/null
+++ b/_Main/BL/Type_NewOfflinePlanCell/Method_FindProductionLineAndType.qbl
@@ -0,0 +1,9 @@
+Quintiq file version 2.0
+#parent: #root
+Method FindProductionLineAndType (
+  String productionLine,
+  String type
+) as Boolean
+{
+  TextBody: 'return this.NewOfflinePlanRow().ProductionLine() = productionLine and this.NewOfflinePlanRow().Type() = type and this.Quantity() > 0.0;'
+}
diff --git a/_Main/BL/Type_NewOfflinePlanCell/StaticMethod_CreateOldOfflinePlanData.qbl b/_Main/BL/Type_NewOfflinePlanCell/StaticMethod_CreateOldOfflinePlanData.qbl
new file mode 100644
index 0000000..17e03bb
--- /dev/null
+++ b/_Main/BL/Type_NewOfflinePlanCell/StaticMethod_CreateOldOfflinePlanData.qbl
@@ -0,0 +1,79 @@
+Quintiq file version 2.0
+#parent: #root
+StaticMethod CreateOldOfflinePlanData (
+  MacroPlan macroPlan
+) as OfflinePlanTable
+{
+  TextBody:
+  [*
+    nopt := maxselect( macroPlan, NewOfflinePlanTable, tempNOPT, true, tempNOPT.SaveDateTime() );
+    
+    macroPlan.OfflinePlanTable( relflush );
+    
+    opt  := macroPlan.OfflinePlanTable( relnew, SaveDateTime := nopt.SaveDateTime() );
+    
+    // 鍒涘缓鍒�
+    firstColumn  := opt.OfflinePlanColumn( relnew, ColumnDate := macroPlan.StartOfPlanning().Date() - 2 );
+    secondColumn := opt.OfflinePlanColumn( relnew, ColumnDate := macroPlan.StartOfPlanning().Date() - 1 );
+    traverse ( nopt, NewOfflinePlanColumn, nopc ) {
+      opt.OfflinePlanColumn( relnew, ColumnDate := nopc.StartDate() );
+    }
+    
+    pls := selectuniquevalues( nopt, NewOfflinePlanRow, tempNOPR, true, tempNOPR.ProductionLine() );
+    
+    traverse ( pls, Elements, pl ) {
+      // 鍒涘缓鏄庣粏琛�
+      detailNOPRs := selectsortedset( nopt, NewOfflinePlanRow, tempNOPR, tempNOPR.ProductionLine() = pl and tempNOPR.Type() = "1", tempNOPR.ProductionLine(), tempNOPR.ProductID() );
+      traverse ( detailNOPRs, Elements, nopr ) {
+        quantityOPR := opt.OfflinePlanRow( relnew, ProductionLine := nopr.ProductionLine(), ProductID := nopr.ProductID(), Type := "1" );
+        orderOPR    := opt.OfflinePlanRow( relnew, ProductionLine := nopr.ProductionLine(), ProductID := nopr.ProductID(), Type := "2" );
+        
+        // 绗竴鍒�
+        quantityOPR.OfflinePlanCell( relnew, OfflinePlanColumn := firstColumn,  Value := nopr.Name() );
+        orderOPR.OfflinePlanCell(    relnew, OfflinePlanColumn := firstColumn,  Value := "" );
+        
+        // 绗簩鍒�
+        quantityOPR.OfflinePlanCell( relnew, OfflinePlanColumn := secondColumn, Value := "Quantity" );
+        orderOPR.OfflinePlanCell(    relnew, OfflinePlanColumn := secondColumn,  Value := "Order" );
+        
+        // 鏃堕棿鍒�
+        traverse ( nopr, NewOfflinePlanCell, nopcell ) {
+          opc := select( opt, OfflinePlanColumn, tempOPC, tempOPC.ColumnDate() = nopcell.StartDate() );
+          quantityOPR.OfflinePlanCell( relnew, OfflinePlanColumn := opc, Value := [String]nopcell.Quantity() );
+          orderOPR.OfflinePlanCell(    relnew, OfflinePlanColumn := opc, Value := nopcell.Order() );
+        }
+      }
+      
+      // 鍒涘缓鍚堣琛�
+      totalNOPR            := select( nopt, NewOfflinePlanRow, tempNOPR, tempNOPR.ProductionLine() = pl and tempNOPR.Type() = "2" );
+      
+      totalOPR             := opt.OfflinePlanRow( relnew, ProductionLine := totalNOPR.ProductionLine(), ProductID := "Z", Type := "3" );
+      shiftPatternNameOPR  := opt.OfflinePlanRow( relnew, ProductionLine := totalNOPR.ProductionLine(), ProductID := "Z", Type := "4" );
+      shiftPatternStartOPR := opt.OfflinePlanRow( relnew, ProductionLine := totalNOPR.ProductionLine(), ProductID := "Z", Type := "5" );
+      shiftPatternEndOPR   := opt.OfflinePlanRow( relnew, ProductionLine := totalNOPR.ProductionLine(), ProductID := "Z", Type := "6" );
+      
+      // 绗竴鍒�
+      totalOPR.OfflinePlanCell( relnew, OfflinePlanColumn := firstColumn, Value := "鍚堣" );
+      shiftPatternNameOPR.OfflinePlanCell( relnew, OfflinePlanColumn := firstColumn, Value := "" );
+      shiftPatternStartOPR.OfflinePlanCell( relnew, OfflinePlanColumn := firstColumn, Value := "" );
+      shiftPatternEndOPR.OfflinePlanCell( relnew, OfflinePlanColumn := firstColumn, Value := "" );
+      
+       // 绗簩鍒�
+      totalOPR.OfflinePlanCell( relnew, OfflinePlanColumn := secondColumn, Value := "鎬婚噺" );
+      shiftPatternNameOPR.OfflinePlanCell( relnew, OfflinePlanColumn := secondColumn, Value := "鐝" );
+      shiftPatternStartOPR.OfflinePlanCell( relnew, OfflinePlanColumn := secondColumn, Value := "鐝寮�濮嬫椂闂�" );
+      shiftPatternEndOPR.OfflinePlanCell( relnew, OfflinePlanColumn := secondColumn, Value := "鐝缁撴潫鏃堕棿" );
+      
+      // 鏃堕棿鍒�
+      traverse ( totalNOPR, NewOfflinePlanCell, nopcell ) {
+        opc := select( opt, OfflinePlanColumn, tempOPC, tempOPC.ColumnDate() = nopcell.StartDate() );
+        totalOPR.OfflinePlanCell(             relnew, OfflinePlanColumn := opc, Value := [String]nopcell.TotalQuantity() );
+        shiftPatternNameOPR.OfflinePlanCell(  relnew, OfflinePlanColumn := opc, Value := nopcell.ShiftPatternName() );
+        shiftPatternStartOPR.OfflinePlanCell( relnew, OfflinePlanColumn := opc, Value := nopcell.ShiftPatternStart() );
+        shiftPatternEndOPR.OfflinePlanCell(   relnew, OfflinePlanColumn := opc, Value := nopcell.ShiftPatternEnd() );
+      }
+    }
+    
+    return opt;
+  *]
+}
diff --git a/_Main/BL/Type_NewOfflinePlanCell/StaticMethod_Download.qbl b/_Main/BL/Type_NewOfflinePlanCell/StaticMethod_Download.qbl
new file mode 100644
index 0000000..69717c3
--- /dev/null
+++ b/_Main/BL/Type_NewOfflinePlanCell/StaticMethod_Download.qbl
@@ -0,0 +1,16 @@
+Quintiq file version 2.0
+#parent: #root
+StaticMethod Download (
+  MacroPlan macroPlan,
+  NewOfflinePlanRows selectedNOPRs
+) as BinaryValue
+{
+  TextBody:
+  [*
+    opt  := NewOfflinePlanCell::CreateOldOfflinePlanData( macroPlan );
+    
+    oprs := selectset( opt, OfflinePlanRow, tempOPR, exists( selectedNOPRs, Elements, tempNOPR, tempNOPR.ProductionLine() = tempOPR.ProductionLine() ) );
+    
+    return NewOfflinePlanCell::OldDownload( macroPlan, oprs );
+  *]
+}
diff --git a/_Main/BL/Type_NewOfflinePlanCell/StaticMethod_Import.qbl b/_Main/BL/Type_NewOfflinePlanCell/StaticMethod_Import.qbl
new file mode 100644
index 0000000..810d73c
--- /dev/null
+++ b/_Main/BL/Type_NewOfflinePlanCell/StaticMethod_Import.qbl
@@ -0,0 +1,61 @@
+Quintiq file version 2.0
+#parent: #root
+StaticMethod Import (
+  NewOfflinePlanTable nopt,
+  GeneralExcelImportAndExportDataTable table
+)
+{
+  TextBody:
+  [*
+    cnv2 := StringToDate::StandardConverter();
+    cnv2.SetCustomConversion();
+    cnv2.CustomFormatString( 'yyyy/MM/dd' );
+    
+    productionLine   := "";
+    productID        := "";
+    isUpdateQuantity := false;
+    isUpdateOrder    := false;
+    
+    traverse ( table, GeneralExcelImportAndExportDataRow, row ) {  
+      traverse ( table, GeneralExcelImportAndExportDataColumn, column ) {
+        // 鍗曞厓鏍煎��
+        cell := select( row, GeneralExcelImportAndExportDataCell, tempGEIAEDCell, tempGEIAEDCell.GeneralExcelImportAndExportDataColumn() = column );
+        
+        // 绗竴鍒楀垽鏂�
+        if ( cell.Value().Tokenize( "_" ).Size() = 2 and column.ColumnIndex() = 0 ) {
+          productionLine := cell.Value().Tokenize( "_" ).Element( 0 );
+          productID      := cell.Value().Tokenize( "_" ).Element( 1 );
+        }
+        
+        // 绗簩鍒楀垽鏂�
+        if ( cell.Value() = "Quantity" and column.ColumnIndex() = 1 ) {
+          isUpdateQuantity := true;
+          isUpdateOrder    := false;
+        } else if ( cell.Value() = "Order" and column.ColumnIndex() = 1 ) {
+          isUpdateQuantity := false;
+          isUpdateOrder    := true;
+        } else if ( cell.Value() <> "Quantity" and cell.Value() <> "Order" and column.ColumnIndex() = 1 ) {
+          isUpdateQuantity := false;
+          isUpdateOrder    := false;
+        }
+        
+        // 鏃堕棿鍒楁洿鏂�
+        if ( isUpdateQuantity and cnv2.CanConvert( column.Name() ) and column.ColumnIndex() > 1 ) {
+          noopcell := select( nopt, NewOfflinePlanRow.NewOfflinePlanCell, tempNOPCell, 
+                              tempNOPCell.NewOfflinePlanRow().ProductionLine() = productionLine and 
+                              tempNOPCell.NewOfflinePlanRow().ProductID()      = productID      and
+                              tempNOPCell.NewOfflinePlanRow().Type()           = "1"            and
+                              tempNOPCell.NewOfflinePlanColumn().StartDate()   = cnv2.Convert( column.Name() ) );
+          noopcell.Quantity( [Number]cell.Value() );
+        } else if ( isUpdateOrder and cnv2.CanConvert( column.Name() ) and column.ColumnIndex() > 1 ) {
+          noopcell := select( nopt, NewOfflinePlanRow.NewOfflinePlanCell, tempNOPCell, 
+                              tempNOPCell.NewOfflinePlanRow().ProductionLine() = productionLine and 
+                              tempNOPCell.NewOfflinePlanRow().ProductID()      = productID      and
+                              tempNOPCell.NewOfflinePlanRow().Type()           = "1"            and
+                              tempNOPCell.NewOfflinePlanColumn().StartDate()   = cnv2.Convert( column.Name() ) );
+          noopcell.Order( cell.Value() );
+        }
+      }
+    }
+  *]
+}
diff --git a/_Main/BL/Type_NewOfflinePlanCell/StaticMethod_OldDownload.qbl b/_Main/BL/Type_NewOfflinePlanCell/StaticMethod_OldDownload.qbl
new file mode 100644
index 0000000..5d57182
--- /dev/null
+++ b/_Main/BL/Type_NewOfflinePlanCell/StaticMethod_OldDownload.qbl
@@ -0,0 +1,49 @@
+Quintiq file version 2.0
+#parent: #root
+StaticMethod OldDownload (
+  MacroPlan macroPlan,
+  OfflinePlanRows selectedOPRs
+) as BinaryValue
+{
+  TextBody:
+  [*
+    xmlDOMI      := XMLDOMImplementation::Create();
+    xmlDOM       := xmlDOMI.CreateDocumentFromString( '<?xml version="1.0" encoding="UTF-16"?><table><name>Sheet1</name></table>' );
+    
+    tableElement := xmlDOM.GetElementByTagName( "table", 0 );
+    
+    opt  := maxselect( macroPlan, OfflinePlanTable, tempOPT, true, tempOPT.SaveDateTime() );
+    opcs := selectsortedset( opt, OfflinePlanColumn, tempOPC, true, tempOPC.ColumnDate() );
+    oprs := selectsortedset( selectedOPRs, Elements, tempOPR, true, tempOPR.ProductionLine(), tempOPR.ProductID(), tempOPR.Type() );
+    traverse ( opcs, Elements, opc ) {
+      column := xmlDOM.CreateElement( "column" );
+      name   := xmlDOM.CreateElement( "name" );
+      type   := xmlDOM.CreateElement( "type" );
+      name.TextContent( ifexpr( opc.ColumnDate() < macroPlan.StartOfPlanning().Date(), "*", opc.ColumnDate().Format( "Y/M2/D2" ) ) );
+      type.TextContent( "String" );
+      column.AppendChild( name );
+      column.AppendChild( type );
+      
+      traverse ( oprs, Elements, opr ) {
+        c    := select( opc, OfflinePlanCell, tempOPC, tempOPC.OfflinePlanRow() = opr );
+        cell := xmlDOM.CreateElement( "cell" );
+        cell.SetAttribute( "value", guard( c.Value(), "" ) );
+        column.AppendChild( cell );
+      }
+      
+      tableElement.AppendChild( column );
+    }
+    
+    xmlString := xmlDOMI.CreateSerializer().WriteToString( xmlDOM )
+    
+    //info( xmlString );
+    
+    tableGroupHandle := TableGroupHandle::Create( "Sheet1" );
+    tableHandle      := TableHandle::ImportXML( BinaryValue::Construct( xmlString ) );
+    tableGroupHandle.Add( tableHandle );
+    
+    binaryData := XLS::SaveTableGroupToBinaryData( tableGroupHandle, true );
+    
+    return binaryData.AsBinaryValue();
+  *]
+}
diff --git a/_Main/BL/Type_NewOfflinePlanCell/StaticMethod_RefreshOfflinePlan.qbl b/_Main/BL/Type_NewOfflinePlanCell/StaticMethod_RefreshOfflinePlan.qbl
index 1d7eafb..a018b69 100644
--- a/_Main/BL/Type_NewOfflinePlanCell/StaticMethod_RefreshOfflinePlan.qbl
+++ b/_Main/BL/Type_NewOfflinePlanCell/StaticMethod_RefreshOfflinePlan.qbl
@@ -8,7 +8,15 @@
   [*
     macroPlan.NewOfflinePlanTable( relflush );
     
-    nopt := macroPlan.NewOfflinePlanTable( relnew, SaveDateTime := DateTime::ActualTime() );
+    nopt         := macroPlan.NewOfflinePlanTable( relnew, SaveDateTime := DateTime::ActualTime() );
+    
+    // 璁″垝寮�濮嬫椂闂�
+    planningDate := macroPlan.StartOfPlanning().Date() 
+    
+    // 鍒濆鍖栦笅绾胯鍒掑垪
+    traverse ( macroPlan, Period_MP, pmp, pmp.IsPlanning() and not pmp.IsHistorical() ) {
+      nopt.NewOfflinePlanColumn( relnew, StartDate := pmp.StartDate(), EndDate := pmp.EndDate() );
+    }
     
     // 鐢熸垚涓嬬嚎璁″垝琛ㄣ�愪竴鏉′骇绾垮湪鍚屼竴澶╀笉浼氫骇鐢熶袱娆$浉鍚屼骇鍝佺殑鍛ㄦ湡浠诲姟銆�
     traverse ( macroPlan, Unit, u, u.HasCapacityTypeTime() 
@@ -48,32 +56,10 @@
             detaileNOPCell := detailedNOPR.NewOfflinePlanCell( relnew, 
                                                                Quantity          := ns.Quantity().Round( 0 ), 
                                                                InventoryWeight   := pispippl.InventoryLevelEnd() - pispippl.MinInventoryLevel(),
-                                                               ShiftPatternName  := guard( sdt.Name(), "" ),
+                                                               ShiftPatternName  := guard( sp.Name(), "" ),
                                                                ShiftPatternStart := guard( sdt.StartDateTime().Format( "H:m" ), "" ),
                                                                ShiftPatternEnd   := guard( sdt.EndDateTIme().Format( "H:m" ), "" ) );
             detaileNOPCell.NewOfflinePlanColumn( relset, nopc );
-          }
-        }
-      }
-      
-      // 璁剧疆鐢熶骇椤哄簭銆愬涓嬬嚎璁″垝鏄庣粏鐢熸晥銆�
-      
-      
-      // 銆愮敓鎴愬悎璁′笅绾胯鍒掋��
-      totalNOPR   := nopt.NewOfflinePlanRow( relnew, ProductID := "All", ProductionLine := u.ID(), Type := "2" );
-      detailNOPRs := selectset( nopt, NewOfflinePlanRow, tempNOPR, tempNOPR.ProductionLine() = u.ID() and tempNOPR.Type() = "1" );
-      traverse ( detailNOPRs, Elements, detailNOPR ) {
-        traverse ( detailNOPR, NewOfflinePlanCell, detailNOPCell ) {
-          // 鐢熶骇涓嬬嚎璁″垝銆愪骇绾垮悎璁°�戝崟鍏冩牸
-          totalNOPRCell   := select( totalNOPR, NewOfflinePlanCell, tempNOPCell, tempNOPCell.NewOfflinePlanColumn() = detailNOPCell.NewOfflinePlanColumn() );
-          if ( isnull( totalNOPRCell ) ) {
-            totalNOPRCell := totalNOPR.NewOfflinePlanCell( relnew );
-            totalNOPRCell.NewOfflinePlanColumn( relset, detailNOPCell.NewOfflinePlanColumn() ); // 璁剧疆鍒�
-            // 灏嗘槑缁嗗崟鍏冩牸鍏宠仈鍒板悎璁″崟鍏冩牸
-            totalNOPRCell.Detailed( relinsert, detailNOPCell );
-          } else {
-            // 灏嗘槑缁嗗崟鍏冩牸鍏宠仈鍒板悎璁″崟鍏冩牸
-            totalNOPRCell.Detailed( relinsert, detailNOPCell );
           }
         }
       }
@@ -88,6 +74,87 @@
           }
         }
       }
+      
+      // 銆愮敓鎴愬悎璁′笅绾胯鍒掋��
+      totalNOPR   := nopt.NewOfflinePlanRow( relnew, ProductID := "All", ProductionLine := u.ID(), Type := "2" );
+      detailNOPRs := selectset( nopt, NewOfflinePlanRow, tempNOPR, tempNOPR.ProductionLine() = u.ID() and tempNOPR.Type() = "1" );
+      traverse ( detailNOPRs, Elements, detailNOPR ) {
+        shiftPatternNOPCell := select( detailNOPR, NewOfflinePlanCell, tempNOPCell, tempNOPCell.ShiftPatternName() <> "" );
+        traverse ( detailNOPR, NewOfflinePlanCell, detailNOPCell ) {
+          // 鐢熶骇涓嬬嚎璁″垝銆愪骇绾垮悎璁°�戝崟鍏冩牸
+          totalNOPRCell   := select( totalNOPR, NewOfflinePlanCell, tempNOPCell, tempNOPCell.NewOfflinePlanColumn() = detailNOPCell.NewOfflinePlanColumn() );
+          if ( isnull( totalNOPRCell ) ) {
+            totalNOPRCell := totalNOPR.NewOfflinePlanCell( relnew, 
+                                                           ShiftPatternName  := shiftPatternNOPCell.ShiftPatternName(),
+                                                           ShiftPatternStart := shiftPatternNOPCell.ShiftPatternStart(),
+                                                           ShiftPatternEnd   := shiftPatternNOPCell.ShiftPatternEnd() );
+            totalNOPRCell.NewOfflinePlanColumn( relset, detailNOPCell.NewOfflinePlanColumn() ); // 璁剧疆鍒�
+            // 灏嗘槑缁嗗崟鍏冩牸鍏宠仈鍒板悎璁″崟鍏冩牸
+            totalNOPRCell.Detailed( relinsert, detailNOPCell );
+          } else {
+            // 灏嗘槑缁嗗崟鍏冩牸鍏宠仈鍒板悎璁″崟鍏冩牸
+            totalNOPRCell.Detailed( relinsert, detailNOPCell );
+          }
+        }
+      }
+      
+      // 璁剧疆鐢熶骇椤哄簭銆愬涓嬬嚎璁″垝鏄庣粏鐢熸晥銆�
+      indexColumn    := select( nopt, NewOfflinePlanColumn, tempNOPC, tempNOPC.StartDate() = planningDate );
+      previousColumn := indexColumn.PreviousNOPColumn();
+      nextColumn     := indexColumn.NextNOPColumn();
+      while ( not isnull( indexColumn.NextNOPColumn() )
+    //          and indexColumn.ColumnDate() <= Date::Construct( 2020, 4, 1 ) // 娴嬭瘯瀹為檯鍦烘櫙鏃跺彲浠ヨ繃婊�
+             ) 
+      {
+        orderNr     := 1;
+        nopcells    := selectsortedset( indexColumn, NewOfflinePlanCell, tempNOPCell, tempNOPCell.FindProductionLineAndType( u.ID(), "1" ), tempNOPCell.InventoryWeight() );
+        initialSize := nopcells.Size();
+    //    info( "璁″垝寮�濮嬫椂闂达細", planningDate.Format( "Y-M2-D2" ), "    绱㈠紩鏃堕棿锛�", indexColumn.StartDate().Format( "Y-M2-D2" ), "    涓暟锛�", nopcells.Size() );
+        
+        if ( nopcells.Size() > 0 ) {
+        
+          // 鍒ゅ畾1
+          previousOPCell := maxselect( previousColumn, NewOfflinePlanCell, tempNOPCell, tempNOPCell.FindProductionLineAndType( u.ID(), "1" ), tempNOPCell.OrderNr() );
+          if ( isnull( previousOPCell ) or indexColumn.StartDate() = planningDate ) {
+            nopcell := nopcells.Element( 0 );
+            nopcell.Order( "#" + orderNr.Format( "N(LPad0(2))" ) );
+            nopcell.OrderNr( orderNr );
+            orderNr++;
+            nopcells.Remove( nopcell );
+          } else {
+            nopcell := select( nopcells, Elements, tempNOPCell, tempNOPCell.NewOfflinePlanRow().ProductID() =  previousOPCell.NewOfflinePlanRow().ProductID() );
+            if ( not isnull( nopcell ) ) {
+              nopcell.Order( "#" + orderNr.Format( "N(LPad0(2))" ) );
+              nopcell.OrderNr( orderNr );
+              orderNr++;
+              nopcells.Remove( nopcell );
+            }
+          }
+          
+          // 鍒ゅ畾2
+          nextNOPCells := selectset( nopcells, Elements, tempOPC,
+                                     exists( nextColumn, NewOfflinePlanCell, nextOPCell, nextOPCell.FindProductionLineAndType( u.ID(), "1" ) and 
+                                             nextOPCell.NewOfflinePlanRow().ProductID() = tempOPC.NewOfflinePlanRow().ProductID() ) ); // 鎵句笅涓�鍒楁槸鍚︽湁褰撳墠鍒楃敓浜х殑浜у搧
+          if ( nextNOPCells.Size() = 1 ) {
+            nopcell := nextNOPCells.Element( 0 );
+            nopcell.Order( "#" + initialSize.Format( "N(LPad0(2))" ) );
+            nopcell.OrderNr( initialSize );
+            nopcells.Remove( nopcell );
+          }
+          
+          // 鍒ゅ畾3
+          traverse ( nopcells, Elements, opcell ) {
+            opcell.Order( "#" + orderNr.Format( "N(LPad0(2))" ) );
+            opcell.OrderNr( orderNr );
+            orderNr++;
+            nopcells.Remove( opcell );
+          }
+        }
+        
+        indexColumn    := indexColumn.NextNOPColumn();
+        previousColumn := indexColumn.PreviousNOPColumn();
+        nextColumn     := indexColumn.NextNOPColumn();
+      }
     }
   *]
 }
diff --git a/_Main/BL/Type_NewOfflinePlanCell/StaticMethod_SaveAsDraft.qbl b/_Main/BL/Type_NewOfflinePlanCell/StaticMethod_SaveAsDraft.qbl
new file mode 100644
index 0000000..79baccb
--- /dev/null
+++ b/_Main/BL/Type_NewOfflinePlanCell/StaticMethod_SaveAsDraft.qbl
@@ -0,0 +1,16 @@
+Quintiq file version 2.0
+#parent: #root
+StaticMethod SaveAsDraft (
+  MacroPlan macroPlan,
+  NewOfflinePlanTable nopt
+) as NewOfflinePlanTable
+{
+  TextBody:
+  [*
+    newTable := nopt.DeepCopy().astype( NewOfflinePlanTable );
+    newNOPT  := macroPlan.NewOfflinePlanTable( relinsert, &newTable );
+    newNOPT.SaveDateTime( DateTime::ActualTime() );
+    
+    return newNOPT;
+  *]
+}
diff --git a/_Main/Sys/Repr/Global/NewOfflinePlanCell.qrp b/_Main/Sys/Repr/Global/NewOfflinePlanCell.qrp
index e2b939c..99b2043 100644
--- a/_Main/Sys/Repr/Global/NewOfflinePlanCell.qrp
+++ b/_Main/Sys/Repr/Global/NewOfflinePlanCell.qrp
@@ -2,18 +2,40 @@
 #parent: #root
 TypeRepresentation NewOfflinePlanCell
 {
+  AttributeRepresentation Order
+  {
+    Alignment: 'Right'
+    AttributeKey: '[413988.0.1603460356]'
+  }
+  AttributeRepresentation Quantity
+  {
+    AttributeKey: '[413988.0.1603460346]'
+    Conditional:
+    [
+      DataRepresentation.Conditional
+      {
+        BackgroundColor: '$FF3399'
+        ConversionBody: ''
+        DefaultBackgroundColor: false
+        InheritConversion: false
+      }
+    ]
+  }
   AttributeRepresentation ShiftPatternEnd
   {
+    Alignment: 'Right'
     AttributeKey: '[413988.0.1603460414]'
     Synonym: '鐝缁撴潫鏃堕棿'
   }
   AttributeRepresentation ShiftPatternName
   {
+    Alignment: 'Right'
     AttributeKey: '[413988.0.1603460388]'
     Synonym: '鐝'
   }
   AttributeRepresentation ShiftPatternStart
   {
+    Alignment: 'Right'
     AttributeKey: '[413988.0.1603460401]'
     Synonym: '鐝寮�濮嬫椂闂�'
   }
diff --git a/_Main/UI/MacroPlannerWebApp/Component_FormNewOfflinePlan/Component_MatrixEditor583.def b/_Main/UI/MacroPlannerWebApp/Component_FormNewOfflinePlan/Component_MatrixEditor583.def
index 83cf21e..731ffa7 100644
--- a/_Main/UI/MacroPlannerWebApp/Component_FormNewOfflinePlan/Component_MatrixEditor583.def
+++ b/_Main/UI/MacroPlannerWebApp/Component_FormNewOfflinePlan/Component_MatrixEditor583.def
@@ -92,6 +92,7 @@
     AllowMultipleAttributes: true
     Columns: 'MatrixEditorColumns473'
     ContextMenu: 'matrixeditorContextMenu267'
+    Editable: false
     Rows: 'MatrixEditorRows569'
     Taborder: 0
   ]
diff --git a/_Main/UI/MacroPlannerWebApp/Component_FormNewOfflinePlan/Component_matrixeditorContextMenu229.def b/_Main/UI/MacroPlannerWebApp/Component_FormNewOfflinePlan/Component_matrixeditorContextMenu229.def
index 0c35e50..05fbee8 100644
--- a/_Main/UI/MacroPlannerWebApp/Component_FormNewOfflinePlan/Component_matrixeditorContextMenu229.def
+++ b/_Main/UI/MacroPlannerWebApp/Component_FormNewOfflinePlan/Component_matrixeditorContextMenu229.def
@@ -3,6 +3,30 @@
 {
   #keys: '[413988.0.1607432887]'
   BaseType: 'matrixeditorContextMenu'
+  Children:
+  [
+    Component mSeparator
+    {
+      #keys: '[413988.0.1651491148]'
+      BaseType: 'WebMenu'
+      Properties:
+      [
+        Separator: true
+        Taborder: 2
+      ]
+    }
+    Component mShowOrHide
+    {
+      #keys: '[413988.0.1651491174]'
+      BaseType: 'WebMenu'
+      Properties:
+      [
+        Image: 'POWER'
+        Taborder: 3
+        Title: 'Show/Hide total'
+      ]
+    }
+  ]
   Properties:
   [
     Taborder: 4
diff --git a/_Main/UI/MacroPlannerWebApp/Component_FormNewOfflinePlan/Component_pHeader.def b/_Main/UI/MacroPlannerWebApp/Component_FormNewOfflinePlan/Component_pHeader.def
index 90182d1..2858b6f 100644
--- a/_Main/UI/MacroPlannerWebApp/Component_FormNewOfflinePlan/Component_pHeader.def
+++ b/_Main/UI/MacroPlannerWebApp/Component_FormNewOfflinePlan/Component_pHeader.def
@@ -15,6 +15,66 @@
         Taborder: 0
       ]
     }
+    Component bDownload id:bDownload_141
+    {
+      #keys: '[413988.0.1652609611]'
+      BaseType: 'WebButton'
+      Properties:
+      [
+        Label: 'Download'
+        Taborder: 1
+      ]
+    }
+    Component bImport id:bImport_217
+    {
+      #keys: '[413988.0.1654241348]'
+      BaseType: 'WebButton'
+      Properties:
+      [
+        Label: 'Import'
+        Taborder: 2
+      ]
+    }
+    Component bDeductChangeoverLoss id:bDeductChangeoverLoss_737
+    {
+      #keys: '[413988.0.1652941817]'
+      BaseType: 'WebButton'
+      Properties:
+      [
+        Label: 'Deduct changeover loss'
+        Taborder: 3
+      ]
+    }
+    Component bRestore id:bRestore_778
+    {
+      #keys: '[413988.0.1654260514]'
+      BaseType: 'WebButton'
+      Properties:
+      [
+        Label: 'Restore'
+        Taborder: 4
+      ]
+    }
+    Component bSaveAsDraft id:bSaveAsDraft_870
+    {
+      #keys: '[413988.0.1654260654]'
+      BaseType: 'WebButton'
+      Properties:
+      [
+        Label: 'Save as draft'
+        Taborder: 5
+      ]
+    }
+    Component bConfirm id:bConfirm_978
+    {
+      #keys: '[413988.0.1654260807]'
+      BaseType: 'WebButton'
+      Properties:
+      [
+        Label: 'Confirm'
+        Taborder: 6
+      ]
+    }
   ]
   Properties:
   [
diff --git a/_Main/UI/MacroPlannerWebApp/Component_FormNewOfflinePlan/Response_matrixeditorContextMenu229_mShowOrHide_OnClick.def b/_Main/UI/MacroPlannerWebApp/Component_FormNewOfflinePlan/Response_matrixeditorContextMenu229_mShowOrHide_OnClick.def
new file mode 100644
index 0000000..15af10a
--- /dev/null
+++ b/_Main/UI/MacroPlannerWebApp/Component_FormNewOfflinePlan/Response_matrixeditorContextMenu229_mShowOrHide_OnClick.def
@@ -0,0 +1,16 @@
+Quintiq file version 2.0
+#parent: matrixeditorContextMenu229/mShowOrHide
+Response OnClick () id:Response_matrixeditorContextMenu229_mShowOrHide_OnClick
+{
+  #keys: '[413988.0.1651491239]'
+  CanBindMultiple: false
+  DefinitionID: 'Responsedef_WebMenu_OnClick'
+  QuillAction
+  {
+    Body:
+    [*
+      pTotal.Visible( not pTotal.Visible() );
+    *]
+    GroupServerCalls: false
+  }
+}
diff --git "a/_Main/UI/MacroPlannerWebApp/Component_FormNewOfflinePlan/Response_pHeader_bDownload_OnClick\043870.def" "b/_Main/UI/MacroPlannerWebApp/Component_FormNewOfflinePlan/Response_pHeader_bDownload_OnClick\043870.def"
new file mode 100644
index 0000000..b54bd15
--- /dev/null
+++ "b/_Main/UI/MacroPlannerWebApp/Component_FormNewOfflinePlan/Response_pHeader_bDownload_OnClick\043870.def"
@@ -0,0 +1,22 @@
+Quintiq file version 2.0
+#parent: pHeader/bDownload_141
+Response OnClick () id:Response_pHeader1_799_bDownload_OnClick_870
+{
+  #keys: '[413988.0.1652609610]'
+  CanBindMultiple: false
+  DefinitionID: 'Responsedef_WebButton_OnClick'
+  Precondition:
+  [*
+    return not isnull( MacroPlan );
+  *]
+  QuillAction
+  {
+    Body:
+    [*
+      binaryValue := NewOfflinePlanCell::Download( MacroPlan, dhSelectedDetailNewOfflinePlanRow.Data() );
+      
+      Application.Download( "Output plan.xlsx", binaryValue.AsBinaryData() );
+    *]
+    GroupServerCalls: false
+  }
+}
diff --git "a/_Main/UI/MacroPlannerWebApp/Component_FormNewOfflinePlan/Response_pHeader_bImport_OnClick\043568.def" "b/_Main/UI/MacroPlannerWebApp/Component_FormNewOfflinePlan/Response_pHeader_bImport_OnClick\043568.def"
new file mode 100644
index 0000000..66ab91b
--- /dev/null
+++ "b/_Main/UI/MacroPlannerWebApp/Component_FormNewOfflinePlan/Response_pHeader_bImport_OnClick\043568.def"
@@ -0,0 +1,45 @@
+Quintiq file version 2.0
+#parent: pHeader/bImport_217
+Response OnClick () id:Response_pHeader1_799_bImport_OnClick_568
+{
+  #keys: '[413988.0.1654241347]'
+  CanBindMultiple: false
+  DefinitionID: 'Responsedef_WebButton_OnClick'
+  Precondition:
+  [*
+    return not isnull( MacroPlan );
+  *]
+  QuillAction
+  {
+    Body:
+    [*
+      try {
+        uploadJsonString := Application.GetFile();
+        if ( uploadJsonString <> "" ) {
+          uploadJson := JSON::Parse( uploadJsonString );
+          Archive::VerifyTheFileName( uploadJson );
+                
+          fileName := uploadJson.Get( "name" ).GetString();
+          base64String := uploadJson.Get( "data" ).GetString();
+              
+          webFileBinaryData := BinaryData::FromBase64EncodedString( base64String ).AsBinaryValue();
+          
+          generalExcelImportAndExportDataSource := GeneralExcelImportAndExportDataSource::Upload( RecycleBin, webFileBinaryData, fileName );
+          generalExcelImportAndExportDataSource.ReadStructure();
+          
+          selection := select( generalExcelImportAndExportDataSource, GeneralExcelImportAndExportDataTable, tempGEIAEDT, tempGEIAEDT.Name() = "Sheet1" );
+          
+          NewOfflinePlanCell::Import( dhNewOfflinePlanTable.Data(), selection );
+          
+          // 鍚庣画鍒犻櫎
+          generalExcelImportAndExportDataSource.Delete();
+          
+          WebMessageBox::Success( Translations::A_VWED_Success() );
+        }
+      } onerror {
+        WebMessageBox::Error( e.GeneralInformation() );
+      }
+    *]
+    GroupServerCalls: false
+  }
+}
diff --git "a/_Main/UI/MacroPlannerWebApp/Component_FormNewOfflinePlan/Response_pHeader_bRestore_OnClick\043141.def" "b/_Main/UI/MacroPlannerWebApp/Component_FormNewOfflinePlan/Response_pHeader_bRestore_OnClick\043141.def"
new file mode 100644
index 0000000..707c104
--- /dev/null
+++ "b/_Main/UI/MacroPlannerWebApp/Component_FormNewOfflinePlan/Response_pHeader_bRestore_OnClick\043141.def"
@@ -0,0 +1,27 @@
+Quintiq file version 2.0
+#parent: pHeader/bRestore_778
+Response OnClick () id:Response_pHeader1_799_bRestore_OnClick_141
+{
+  #keys: '[413988.0.1654260513]'
+  CanBindMultiple: false
+  DefinitionID: 'Responsedef_WebButton_OnClick'
+  Precondition:
+  [*
+    return not isnull( MacroPlan );
+  *]
+  QuillAction
+  {
+    Body:
+    [*
+      currentNOPT := dhNewOfflinePlanTable.Data();
+      targetNOPT  := maxselect( MacroPlan, NewOfflinePlanTable, tempNOPT, tempNOPT <> currentNOPT, tempNOPT.SaveDateTime() );
+      if ( not isnull( targetNOPT ) ) {
+        currentNOPT.Delete();
+        dhNewOfflinePlanTable.Data( targetNOPT );
+      }
+      
+      WebMessageBox::Success( Translations::A_VWED_Success() );
+    *]
+    GroupServerCalls: false
+  }
+}
diff --git "a/_Main/UI/MacroPlannerWebApp/Component_FormNewOfflinePlan/Response_pHeader_bSaveAsDraft_OnClick\043870.def" "b/_Main/UI/MacroPlannerWebApp/Component_FormNewOfflinePlan/Response_pHeader_bSaveAsDraft_OnClick\043870.def"
new file mode 100644
index 0000000..fd3f56c
--- /dev/null
+++ "b/_Main/UI/MacroPlannerWebApp/Component_FormNewOfflinePlan/Response_pHeader_bSaveAsDraft_OnClick\043870.def"
@@ -0,0 +1,23 @@
+Quintiq file version 2.0
+#parent: pHeader/bSaveAsDraft_870
+Response OnClick () id:Response_pHeader1_799_bSaveAsDraft_OnClick_870
+{
+  #keys: '[413988.0.1654260653]'
+  CanBindMultiple: false
+  DefinitionID: 'Responsedef_WebButton_OnClick'
+  Precondition:
+  [*
+    return not isnull( MacroPlan );
+  *]
+  QuillAction
+  {
+    Body:
+    [*
+      nopt := NewOfflinePlanCell::SaveAsDraft( MacroPlan, dhNewOfflinePlanTable.Data() );
+      dhNewOfflinePlanTable.Data( nopt );
+      
+      WebMessageBox::Success( Translations::A_VWED_Success() );
+    *]
+    GroupServerCalls: false
+  }
+}
diff --git a/_Main/UI/MacroPlannerWebApp/Component_FormOfflinePlan/Component_matrixeditorContextMenu204.def b/_Main/UI/MacroPlannerWebApp/Component_FormOfflinePlan/Component_matrixeditorContextMenu204.def
index 37b2b4c..26ddf2c 100644
--- a/_Main/UI/MacroPlannerWebApp/Component_FormOfflinePlan/Component_matrixeditorContextMenu204.def
+++ b/_Main/UI/MacroPlannerWebApp/Component_FormOfflinePlan/Component_matrixeditorContextMenu204.def
@@ -3,6 +3,30 @@
 {
   #keys: '[413988.0.1296803057]'
   BaseType: 'matrixeditorContextMenu'
+  Children:
+  [
+    Component mCreateData
+    {
+      #keys: '[413988.0.1652900385]'
+      BaseType: 'WebMenu'
+      Properties:
+      [
+        Image: 'DATA'
+        Taborder: 3
+        Title: '鏈�鏂板垱寤烘暟鎹柟娉�'
+      ]
+    }
+    Component mSparator
+    {
+      #keys: '[413988.0.1652900411]'
+      BaseType: 'WebMenu'
+      Properties:
+      [
+        Separator: true
+        Taborder: 2
+      ]
+    }
+  ]
   Properties:
   [
     Taborder: 4
diff --git a/_Main/UI/MacroPlannerWebApp/Component_FormOfflinePlan/Response_matrixeditorContextMenu204_mCreateData_OnClick.def b/_Main/UI/MacroPlannerWebApp/Component_FormOfflinePlan/Response_matrixeditorContextMenu204_mCreateData_OnClick.def
new file mode 100644
index 0000000..4882a9c
--- /dev/null
+++ b/_Main/UI/MacroPlannerWebApp/Component_FormOfflinePlan/Response_matrixeditorContextMenu204_mCreateData_OnClick.def
@@ -0,0 +1,25 @@
+Quintiq file version 2.0
+#parent: matrixeditorContextMenu204/mCreateData
+Response OnClick () id:Response_matrixeditorContextMenu204_mCreateData_OnClick
+{
+  #keys: '[413988.0.1652970833]'
+  CanBindMultiple: false
+  DefinitionID: 'Responsedef_WebMenu_OnClick'
+  Precondition:
+  [*
+    return not isnull( MacroPlan );
+  *]
+  QuillAction
+  {
+    Body:
+    [*
+      NewOfflinePlanCell::CreateOldOfflinePlanData( MacroPlan );
+      
+      opt := maxselect( MacroPlan, OfflinePlanTable, tempOPT, true, tempOPT.SaveDateTime() );
+      dhOfflinePlanTable.Data( opt );
+      
+      WebMessageBox::Success( Translations::A_VWED_Success() );
+    *]
+    GroupServerCalls: false
+  }
+}

--
Gitblit v1.9.3