1C:Enterprise 8.3. Practical Developer’s Guide. Lesson 24 (6:10). Data exchange. Universal data exchange

Universal data exchange

Suppose that Jack of All Trades established a branch in town N, and installed a configuration identical to yours to manage the branch operations.

You need to establish data exchange between these two databases, so that each database stores full information about materials and services, while accounting and payroll are managed independently in each database.

To do so, you will create an exchange plan, specify which data is included in the exchange, and create a number of procedures that write exchange files to the hard disk and load data from these exchange files.

To simplify the example, you will not develop any automatic file exchange between the two databases. Instead, you will start the exchange procedure manually.

Before you start programming the exchange algorithm, let us discuss some enhancements that you need to make in your database to prepare it for the exchange.

These enhancements are related to the fact that, up to this point, you have only worked with a single database, and have relied on uniqueness of catalog code numbers and document numbers. Now, new catalog items and new documents will be created independently in two databases at the same time, and you still need to ensure uniqueness of catalog item code numbers and document numbers, but this time it’s across two databases.

If you do not do that, there can be a situation when new documents created in both infobases have identical numbers. This will cause a data exchange conflict because the platform attempts to write a document to the database using a number that is already used by another document.

To prevent such situations, let us add a unique prefix that unambiguously identifies the database to document numbers and catalog codes.

Hence even if the numbers of new documents in the two databases match, they will still have different prefixes and no conflict will occur.

You will use the Constant configuration object to store number prefixes.

Creating a constant for data exchange

Updating configuration objects involved in data exchange

Adding an exchange plan

Creating data exchange procedures

Testing data exchange

In Designer mode

Attention! Since during the previous lesson you created a list of users in the configuration, when you start Designer you are prompted for a user name. Enter Administrator as the user name because this user has full access to all the configuration objects. There is no need to enter a password because you have not created one.

The Constant configuration object is used to create database tables for storing data that is never changed over time or is rarely changed.

Each Constant configuration object defines a table that stores a single value.

Let us proceed to creating a constant for storing the number prefix value.

  1. Open Designer and create a Constant configuration object named NumberingPrefix.
  2. Set its data type to String with a fixed length of 2 characters.

In Designer mode

The first thing you need to do is modify the modules of all the objects that are involved in exchanges (these objects include documents, catalogs, and charts of characteristic types).

After the changes document numbers, catalog codes, and chart of accounts codes will be generated based on the value of the NumberingPrefix constant to ensure that such numbers and codes are unique.

Let us keep the function that generates the numbering prefix in a common module because in the future you might need to change the algorithm used to generate document prefixes.

  1. Add a common module named Exchange.
  2. Add the function shown in listing 24.1 to the module.

    Listing 24.1. Function that generates a number prefix

    Function GetNumberingPrefix() Export
        Return Constants.NumberingPrefix.Get();
    EndFunction
    As you can see, the function merely returns the value of the NumberingPrefix constant.

    Now let us update the Customers catalog.
  3. In the configuration object tree, right-click the Customers catalog and then click Open object module.
  4. Add the OnSetNewCode event handler as shown in listing 24.2.

    Listing 24.2. OnSetNewCode event handler

    Procedure OnSetNewCode(StandardProcessing, Prefix)
        Prefix = Exchange.GetNumberingPrefix();
    EndProcedure
    The OnSetNewCode event occurs when a new code is set for a catalog item. Note that you are creating this script in the object module instead of the form module because this event occurs for the object in general instead of some particular form.

    Prefix is the second parameter passed to the handler. A prefix is populated in this procedure, and the platform generates a code based on the prefix.

    The event handler calls a function of a common module. Since the module is not global, you address the function using the module name and the function name: Exchange.GetNumberingPrefix. In this procedure you assign the value of the NumberingPrefix constant to the prefix.
  5. Add similar handlers to all the catalogs and charts of characteristic types involved in exchanges:

    Catalogs:
    • Employees
    • MaterialsAndServices
    • Warehouses
    • MaterialOptions
    • AdditionalMaterialProperties
    Chart of characteristic types:
    • MaterialProperties
  6. For all these objects and for the Customers catalog, in the configuration object property palette increase code length to 11 characters.

    Now let us update the documents.
  7. In the GoodsReceipt document module, add the OnSetNewNumber event handler as shown in listing 24.3.

    Listing 24.3. OnSetNewNumber event handler

    Procedure OnSetNewNumber(StandardProcessing, Prefix)
        Prefix = Exchange.GetNumberingPrefix();
    EndProcedure
  8. Add similar handlers to all the documents involved in exchanges (in your configuration it is only the Services document).
  9. For both documents, in the configuration object property palette increase the number length to 11 characters.

    This completes the preparations for existing configuration objects, and you can move on to the development of data exchange procedures.

In Designer mode

Let us create the central part of a data exchange algorithm: the exchange plan.

  1. In the configuration object tree, expand the Common branch and add an ExchangePlan configuration object named Branches.
  2. In the Object presentation field, enter Branch.
  3. On the Data tab, create an exchange plan attribute named Main, of the Boolean type (fig. 24.2).

    Lesson 24 (6:10). Data exchange / Universal data exchange / Adding an exchange plan / In Designer mode
    Fig. 24.2. Exchange plan attribute

    You will need this attribute to resolve collisions during data exchanges. A collision is a situation where a data exchange object is simultaneously modified in two nodes.

    In this case you will look into the value of the Main attribute and confirm the changes only if they are made in the main node. In the event of a collision, you will reject the changes made anywhere but in the main node.

    Let us define the set of objects that participate in the exchange.
  4. On the Main tab, click Content.
  5. Include all the objects not related to accounting and payroll calculation into the exchange (fig. 24.3).

    Note that the NumberingPrefix constant is not included in the exchange, since its value should be unique for each database that takes part in the exchange.

    Lesson 24 (6:10). Data exchange / Universal data exchange / Adding an exchange plan / In Designer mode
    Fig. 24.3. Data exchange content
  6. On the Forms tab, in the Node field, click the Open Lesson 24 (6:10). Data exchange / Universal data exchange / Adding an exchange plan / In Designer mode button and create the default node form (fig. 24.4).

    Lesson 24 (6:10). Data exchange / Universal data exchange / Adding an exchange plan / In Designer mode
    Fig. 24.4. Creating an exchange plan node form
  7. In the form controls pane, right-click the Form root item, point to Events, and click <OnCreateAtServer>.

    This creates the OnCreateAtServer event handler template. You need this handler to prohibit setting the Main attribute for the predefined node that corresponds to this infobase.
  8. Add the script shown in listing 24.4 to the handler.

    Listing 24.4. OnCreateAtServer form event handler

    &AtServer
    Procedure OnCreateAtServer(Cancel, StandardProcessing)
            
        If Object.Ref = ExchangePlans.Branches.ThisNode() Then
            Items.Main.Enabled = False;
        EndIf;
     
    EndProcedure
    This procedure calls the ThisNode() method of the exchange plan manager, which returns a reference to the exchange plan node that corresponds to the current infobase.
  9. Create a default exchange plan list form.

    You need this form to define some operations related to registering a new exchange node in this form.

    The main goal of these operations is generating all the change registration records for all configuration objects included in the exchange plan during the registration of a new exchange node. This basically serves as initial synchronization of the exchange node with all the exchange data.
  10. On the Commands tab, create a command named WriteChanges.
  11. In the property palette that is opened, in the Action field, click the Open Lesson 24 (6:10). Data exchange / Universal data exchange / Adding an exchange plan / In Designer mode button.
  12. In the dialog box that propmts you to select handler type, click Create on client and a procedure on server (no context).

    This creates two procedures in the form module: a client WriteChanges() procedure and a server out-of-context WriteChangesAtServer procedure, which is called from the client procedure.

    As you already know, out-of-context server procedures are executed much faster than those having a context because they do not require passing the entire form context from the client to the server.

    You need to pass a reference to the ExchangePlan.Branches object as a parameter of the WriteChangesAtServer procedure. You can do it using the CurrentRow property of the List table (the table uses a dynamic list of nodes of the Branches exchange plan as its data source).
  13. Update the module text as shown in listing 24.5.

    Listing 24.5. WriteChanges() command event handler

    &AtServerNoContext
    Procedure WriteChangesAtServer(Node)
        // Insert handler contents.
    EndProcedure
     
    &AtClient
    Procedure WriteChanges(Command)
        WriteChangesAtServer(Items.List.CurrentRow);
    EndProcedure
  14. Add the script shown in listing 24.6 to the WriteChangesAtServer() procedure.

    Listing 24.6. WriteChangesAtServer() procedure

    &AtServerNoContext
        Procedure WriteChangesAtServer(Node)
        // Writing all data changes for a node
        ExchangePlans.RecordChanges(Node);
    EndProcedure
    This procedure utilizes the change registration feature by calling the RecordChanges() method of the exchange plan manager.

    A reference to the current exchange plan node (Branches) is passed to this method.

    The procedure execution creates change registration records in the infobase. These records include the changes that will be sent to the new infobase for each object included in the exchange plan content.
  15. Click the Form tab and drag the WriteChanges command from the Commands tab to the form command bar (fig. 24.5).

    Lesson 24 (6:10). Data exchange / Universal data exchange / Adding an exchange plan / In Designer mode
    Fig. 24.5. Exchange plan list form editor

    Note that the command name in the tree of form items does not exactly match the actual command name. The platform added the "Form" prefix to it, and the button linked to the command also has this name. You will use this name to access the button from 1C:Enterprise script (see listing 24.8).

    Also note that the button should only be available if the current node is not a predefined node for this infobase because otherwise it is impossible to record changes.

    To ensure such button behavior, let us create a function in the list form module that is executed on the server and returns True when the node passed to the function is a predefined one.
  16. Add the function shown in listing 24.7 to the list form module.

    Listing 24.7. PredefinedNode() function

    &AtServerNoContext
    Function PredefinedNode(Node)
        Return Node = ExchangePlans.Branches.ThisNode();
    EndFunction
  17. In the form controls pane, right-click the List item, open its property palette, and create the OnActivateRow event handler that is executed on the client.
  18. Populate the event handler as shown in listing 24.8.

    Listing 24.8. OnActivateRow() event handler of a list form item

    &AtClient
    Procedure ListOnActivateRow(Item)
     
        If PredefinedNode(Item.CurrentRow) Then
            Items.FormWriteChanges.Enabled = False;
     
        Else 
            Items.FormWriteChanges.Enabled = True;
     
        EndIf;
     
    EndProcedure
    This procedure sets the availability of the WriteChanges button based on the return value of the PredefinedNode() function, which is in turn based on the reference to the current node (Item.CurrentRow) passed to the function.

    This completes the exchange plan creation and now you can proceed to the creation of data exchange procedures.

In Designer mode

Let us create a data processor that starts data exchange.

  1. In the configuration object tree, add a DataProcessor configuration object named DataExchange.
  2. On the Forms tab, create a default data processor form.
  3. In the form editor, on the Commands tab, create a form command named StartDataExchange.
  4. In the command property palette, in the Action field, click the Open Lesson 24 (6:10). Data exchange / Universal data exchange / Creating data exchange procedures / In Designer mode button.
  5. In the dialog box that prompts you to select handler type, click Create on client and a procedure on server (no context).

    This creates two procedures in the form module: a client StartDataExchange() procedure and a server out-of-context StartDataExchangeAtServer procedure, which is called from the client procedure.

    The StartDataExchange() procedure has the following text (listing 24.9).

    Listing 24.9. StartDataExchange() command handler

    &AtClient
    Procedure StartDataExchange(Command)
        StartDataExchangeAtServer();
    EndProcedure
  6. Add the following script to the StartDataExchangeAtServer procedure (listing 24.10).

    Listing 24.10. Creating the StartDataExchangeAtServer procedure

    &AtServerNoContext
    Procedure StartDataExchangeAtServer() Export
     
        NodeSelection = ExchangePlans.Branches.Select();
     
        While NodeSelection.Next() Do
     
            // Exchanging data with all nodes, except for the current node (ThisNode)
            If NodeSelection.Ref <> ExchangePlans.Branches.ThisNode() Then
                NodeObject = NodeSelection.GetObject();
     
                // Receiving message
                NodeObject.ReadMessageWithChanges();
     
                // Generating message
                NodeObject.WriteMessageWithChanges();
     
            EndIf;
        EndDo;
     
    EndProcedure
    The procedure has the following algorithm: in the loop it iterates through the nodes of the Branches exchange plan, and for all the nodes except the local node it first reads the messages received from other exchange nodes (you will create the ReadMessagesWithChanges procedure later).

    Next it generates the messages to be sent that contain the modified data for this node (you will create the WriteMessagesWithChanges procedure later).
  7. Drag the StartDataExchange command from the Commands tab to the form controls pane.

    The resulting data processor form should look as follows (fig. 24.6).

    Lesson 24 (6:10). Data exchange / Universal data exchange / Creating data exchange procedures / In Designer mode
    Fig. 24.6. Data processor form

Data writing procedure

Let us store the procedures that write and read exchange data in the object module of the exchange plan.

  1. In the editor of the Branches exchange plan configuration object, click the Other tab and then click Object module.

    Let us create the WriteMessageWithChanges procedure. This will take several steps.

    First, let us implement the name generation for the file that will store the exchange data, and user notifications for the beginning and end of data export.
  2. Add the script shown in listing 24.11 to the module.

    Listing 24.11. Generating a file name in the data writing procedure

    Procedure WriteMessageWithChanges() Export
        
        Message = New UserMessage;
        Message.Text = "-------- Starting export to node: " + String(ThisObject) + " --------";
        Message.Message();
        Directory = TempFilesDir();
     
        // Generating temporary file name
        FileName = Directory + ?(Right(Directory,1)= "", "", "") + "Message"
            + TrimAll(ExchangePlans.Branches.ThisNode().Code) + "_" + TrimAll(Ref.Code)
            + ".xml";
        Message = New UserMessage;
        Message.Text = "-------- Export completed --------";
        Message.Message();
     
    EndProcedure
    To simplify the example, let us exchange messages through a temporary files directory. The message names are standardized and follow the pattern: MessageSourceNodeCode_TargetNodeCode.xml.

    Now let us use the XML document read/write feature. The next procedure part creates an XMLWriter object. Then it uses the created object for opening a new XML file for writing and writes an XML declaration to this file. Finally, it closes the file.
  3. Update the procedure as shown in listing 24.12.

    Listing 24.12. Creating an XMLWriter object in the data writing procedure

    Procedure WriteMessageWithChanges() Export
        
        Message = New UserMessage;
        Message.Text = "-------- Starting export to node: " + String(ThisObject) + " --------";
        Message.Message();
        Directory = TempFilesDir();
    
        // Generating temporary file name
        FileName = Directory + ?(Right(Directory,1)= "", "", "") + "Message"
            + TrimAll(ExchangePlans.Branches.ThisNode().Code) + "_" + TrimAll(Ref.Code)
            + ".xml";
     
        // Creating XMLWriter object
        // *** Writing XML documents
        XMLWriter = New XMLWriter;
        XMLWriter.OpenFile(FileName);
        XMLWriter.WriteXMLDeclaration();
        XMLWriter.Close(); 
     
        Message = New UserMessage;
        Message.Text = "-------- Export completed --------";
        Message.Message();
     
    EndProcedure
    Now let us implement the message infrastructure. The next procedure part creates an ExchangeMessageWriter object. Its BeginWrite() method creates the next message number and writes the message header to the XML file. Finally, the procedure ends the message writing.
  4. Update the procedure as shown in listing 24.13.

    Listing 24.13. Creating the message sequential number and writing the message header to the XML file

    Procedure WriteMessageWithChanges() Export
        
        Message = New UserMessage;
        Message.Text = "-------- Starting export to node: " + String(ThisObject) + " --------";
        Message.Message();
        Directory = TempFilesDir();
     
        // Generating temporary file name
        FileName = Directory + ?(Right(Directory,1)= "", "", "") + "Message"
            + TrimAll(ExchangePlans.Branches.ThisNode().Code) + "_" + TrimAll(Ref.Code)
            + xml";
     
        // Creating XMLWriter object
        // *** Writing XML documents
        XMLWriter = New XMLWriter;
        XMLWriter.OpenFile(FileName);
        XMLWriter.WriteXMLDeclaration();
        
        // *** Message infrastructure
        ExchangeMessageWriter = ExchangePlans.CreateMessageWriter();
        ExchangeMessageWriter.BeginWrite(XMLWriter, Ref);
        Message = New UserMessage;
        Message.Text = "Message number: " + ExchangeMessageWriter.MessageNo;
        Message.Message();
        ExchangeMessageWriter.EndWrite(); 
     
        XMLWriter.Close();
     
        Message = New UserMessage;
        Message.Text = "-------- Export completed --------";
        Message.Message();
     
    EndProcedure
    Since the procedure is located in the object module, you can use the standard Ref attribute as a reference to the Branches exchange plan object.

    In order to get the data for storing to the file, let us use the change registration feature. The next procedure part gets a selection of change registration records intended for this node. The method that generates the selection accepts the message number as its second parameter.
  5. Update the procedure as shown in listing 24.14.

    Listing 24.14. Getting a selection of change registration records intended for this node

    Procedure WriteMessageWithChanges() Export
        
        Message = New UserMessage;
        Message.Text = "-------- Starting export to node: " + String(ThisObject) + " --------";
        Message.Message();
        Directory = TempFilesDir();
     
        // Generating temporary file name
        FileName = Directory + ?(Right(Directory,1)= "", "", "") + "Message"
            + TrimAll(ExchangePlans.Branches.ThisNode().Code) + "_" + TrimAll(Ref.Code)
            + ".xml";
     
        // Creating XMLWriter object
        // *** Writing XML documents
        XMLWriter = New XMLWriter;
        XMLWriter.OpenFile(FileName);
        XMLWriter.WriteXMLDeclaration();
        
        // *** Message infrastructure
        ExchangeMessageWriter = ExchangePlans.CreateMessageWriter();
        ExchangeMessageWriter.BeginWrite(XMLWriter, Ref);
        Message = New UserMessage;
        Message.Text = "Message number: " + ExchangeMessageWriter.MessageNo;
        Message.Message();
     
        // Getting the selection of changed data
        // *** Change registration
        SelectionOfChanges = ExchangePlans.SelectChanges(ExchangeMessageWriter.Recipient,
            ExchangeMessageWriter.MessageNo);
     
        ExchangeMessageWriter.EndWrite(); 
        XMLWriter.Close();
     
        Message = New UserMessage;
        Message.Text = "-------- Export completed --------";
        Message.Message();
     
    EndProcedure
    All that is left to do is adding a loop for selecting records and serializing them to the XML file that is open.
  6. Update the procedure as shown in listing 24.15.

    Listing 24.15. Iterating through the record selection and serializing records to the XML file that is open

    Procedure WriteMessageWithChanges() Export
        
        Message = New UserMessage;
        Message.Text = "-------- Starting export to node: " + String(ThisObject) + " --------";
        Message.Message();
        Directory = TempFilesDir();
     
        // Generating temporary file name
        FileName = Directory + ?(Right(Directory,1)= "", "", "") + "Message"
            + TrimAll(ExchangePlans.Branches.ThisNode().Code) + "_" + TrimAll(Ref.Code)
            + ".xml";
     
        // Creating XMLWriter object
        // *** Writing XML documents
        XMLWriter = New XMLWriter;
        XMLWriter.OpenFile(FileName);
        XMLWriter.WriteXMLDeclaration();
        
        // *** Message infrastructure
        ExchangeMessageWriter = ExchangePlans.CreateMessageWriter();
        ExchangeMessageWriter.BeginWrite(XMLWriter, Ref);
        Message = New UserMessage;
        Message.Text = "Message number: " + ExchangeMessageWriter.MessageNo;
        Message.Message();
     
        // Getting the selection of changed data
        // *** Change registration
        SelectionOfChanges = ExchangePlans.SelectChanges(ExchangeMessageWriter.Recipient,
            ExchangeMessageWriter.MessageNo);
     
        While SelectionOfChanges.Next() Do
            // Writing data to the message
            // ***XML serialization
            WriteXML(XMLWriter, SelectionOfChanges.Get());
        EndDo;
     
        ExchangeMessageWriter.EndWrite(); 
        XMLWriter.Close();
     
        Message = New UserMessage;
        Message.Text = "-------- Export completed --------";
        Message.Message();
     
    EndProcedure
    This completes the procedure for writing exchange data.

Data reading procedure

The steps for creating the procedure that reads exchange data will be similar to the steps for creating the procedure that writes exchange data. First, let us generate the name of the file that will store exchange data.

  1. Add the procedure shown in listing 24.16 to the Branches configuration object module.

    Listing 24.16. Generating a data exchange file name

    Procedure ReadMessageWithChanges() Export
     
        Directory = TempFilesDir();
     
        // Generating file name
        FileName = Directory + ?(Right(Directory,1)= "", "", "") + "Message"
            + TrimAll(Ref.Code) + "_" + TrimAll(ExchangePlans.Branches.ThisNode().Code)
            + ".xml";
        File = New File(FileName);
        If Not File.Exist() Then
            Return;
        EndIf;
     
        DeleteFiles(FileName);
        
        Message = New UserMessage;
        Message.Text = "-------- Import completed --------";
        Message.Message();
     
    EndProcedure
    The procedure generates the name of the file that it expects to find in the directory and then creates a File object with that name and checks whether the file exists. If there is no such file, the procedure execution is terminated. If the file is found, the procedure deletes the file once it processes all its data.

    Let us add the procedure part that reads the exchange data file.
  2. Update the procedure as shown in listing 24.17.

    Listing 24.17. Adding the reading of the exchange data file

    Procedure ReadMessageWithChanges() Export
        
        Directory = TempFilesDir(); 
     
        // Generating file name
        FileName = Directory + ?(Right(Directory,1)= "", "", "") + "Message"
            + TrimAll(Ref.Code) + "_" + TrimAll(ExchangePlans.Branches.ThisNode().Code) 
            + ".xml";
        File = New File(FileName);
        If Not File.Exist() Then
            Return;
        EndIf;
     
        // *** Reading XML document
        // Trying to open the file
        XMLReader = New XMLReader;
        Try
            XMLReader.OpenFile(FileName);
     
        Except
            Message = New UserMessage;
            Message.Text = "Cannot open the data exchange file.";
            Message.Message();
     
            Return;
     
        EndTry;
     
        Message = New UserMessage;
        Message.Text = "-------- Starting import from: " + String(ThisObject) + "---------";
        Message.Message();
        Message = New UserMessage;
        Message.Text = "– Reading file: " + FileName;
        Message.Message();
        
        XMLReader.Close(); 
    
        DeleteFiles(FileName);
     
        Message = New UserMessage;
        Message.Text = "-------- Import completed --------";
        Message.Message();
     
    EndProcedure
    The added procedure part utilizes the XML document read/write feature, which works with files at the basic level.

    First, it creates an XMLReader object that opens the file for reading. If the file is successfully opened, a message is displayed informing the user that the data import begins. At the end the procedure stops reading data from the XML file using the Close() method.

    The data received in this manner should be some sort of data exchange message. To present such data in message terms, let us add the next procedure part.
  3. Update the procedure as shown in listing 24.18.

    Listing 24.18. Adding the reading of XML message header

    Procedure ReadMessageWithChanges() Export
        
        Directory = TempFilesDir(); 
     
        // Generating file name
        FileName = Directory + ?(Right(Directory,1)= "", "", "") + "Message"
            + TrimAll(Ref.Code) + "_" + TrimAll(ExchangePlans.Branches.ThisNode().Code) 
            + ".xml";
        File = New File(FileName);
        If Not File.Exist() Then
            Return;
        EndIf;
     
        // *** Reading XML document
        // Trying to open the file
        XMLReader = New XMLReader;
        Try
            XMLReader.OpenFile(FileName);
     
        Except
            Message = New UserMessage;
            Message.Text = "Cannot open the data exchange file.";
            Message.Message();
     
            Return;
     
        EndTry;
     
        Message = New UserMessage;
        Message.Text = "-------- Starting import from: " + String(ThisObject) + "---------";
        Message.Message();
        Message = New UserMessage;
        Message.Text = "– Reading file: " + FileName;
        Message.Message();
     
        // Importing data from the file
        // *** Message infrastructure
        ExchangeMessageReader = ExchangePlans.CreateMessageReader();
     
        // Reading the data exchange message header from the XML file
        ExchangeMessageReader.BeginRead(XMLReader);
     
        ExchangeMessageReader.EndRead();
     
        XMLReader.Close(); 
    
        DeleteFiles(FileName);
     
        Message = New UserMessage;
        Message.Text = "-------- Import completed --------";
        Message.Message();
     
    EndProcedure
    Here the procedure utilizes the exchange plan message infrastructure and creates an ExchangeMessageReader object. The BeginRead() method of this object reads the XML message header, which contains information about the message sender among other data. Once the entire message is processed, the reading is stopped.

    Now that exchange data is presented as a message and the header is received, let us make one more check before actual data processing begins.
  4. Update the procedure as shown in listing 24.19.

    Listing 24.19. Adding a message check

    Procedure ReadMessageWithChanges() Export
        
        Directory = TempFilesDir(); 
     
        // Generating file name
        FileName = Directory + ?(Right(Directory,1)= "", "", "") + "Message"
            + TrimAll(Ref.Code) + "_" + TrimAll(ExchangePlans.Branches.ThisNode().Code) 
            + ".xml";
        File = New File(FileName);
        If Not File.Exist() Then
            Return;
        EndIf;
     
        // *** Reading XML document
        // Trying to open the file
        XMLReader = New XMLReader;
        Try
            XMLReader.OpenFile(FileName);
     
        Except
            Message = New UserMessage;
            Message.Text = "Cannot open the data exchange file.";
            Message.Message();
     
            Return;
     
        EndTry;
     
        Message = New UserMessage;
        Message.Text = "-------- Starting import from: " + String(ThisObject) + "---------";
        Message.Message();
        Message = New UserMessage;
        Message.Text = "– Reading file: " + FileName;
        Message.Message();
     
        // Importing data from the file
        // *** Message infrastructure
        ExchangeMessageReader = ExchangePlans.CreateMessageReader();
     
        // Reading the data exchange message header from the XML file
        ExchangeMessageReader.BeginRead(XMLReader);
     
        // The message is not for this node
        If ExchangeMessageReader.Sender <> Ref Then
            Raise "Incorrect node";
        EndIf;
     
        ExchangeMessageReader.EndRead();
        XMLReader.Close();
        DeleteFiles(FileName);
     
        Message = New UserMessage;
        Message.Text = "-------- Import completed --------";
        Message.Message();
     
    EndProcedure
    Here the procedure checks whether the message sender is exactly the exchange plan node processed by the current procedure call.

    If the node is correct, before actual data reading begins, you have to delete all the change registration records made for this node that correspond to message numbers less than or equal to the received message number specified in the message being processed. The idea is to prevent duplicating data that is already sent to the node and processed by it.
  5. Update the procedure as shown in listing 24.20.

    Listing 24.20. Deleting change registration records for the source node

    Procedure ReadMessageWithChanges() Export
        
        Directory = TempFilesDir(); 
     
        // Generating file name
        FileName = Directory + ?(Right(Directory,1)= "", "", "") + "Message"
            + TrimAll(Ref.Code) + "_" + TrimAll(ExchangePlans.Branches.ThisNode().Code) 
            + ".xml";
        File = New File(FileName);
        If Not File.Exist() Then
            Return;
        EndIf;
     
        // *** Reading XML document
        // Trying to open the file
        XMLReader = New XMLReader;
        Try
            XMLReader.OpenFile(FileName);
     
        Except
            Message = New UserMessage;
            Message.Text = "Cannot open the data exchange file.";
            Message.Message();
     
            Return;
     
        EndTry;
     
        Message = New UserMessage;
        Message.Text = "-------- Starting import from: " + String(ThisObject) + "---------";
        Message.Message();
        Message = New UserMessage;
        Message.Text = "– Reading file: " + FileName;
        Message.Message();
     
        // Importing data from the file
        // *** Message infrastructure
        ExchangeMessageReader = ExchangePlans.CreateMessageReader();
     
        // Reading the data exchange message header from the XML file
        ExchangeMessageReader.BeginRead(XMLReader);
     
        // The message is not for this node
        If ExchangeMessageReader.Sender <> Ref Then
            Raise "Incorrect node";
        EndIf;
     
        // Deleting change records for the source node
        // *** Change registration service
        ExchangePlans.DeleteChangeRecords(ExchangeMessageReader.Sender,
            ExchangeMessageReader.ReceivedNo);
     
        ExchangeMessageReader.EndRead();
        XMLReader.Close();
        DeleteFiles(FileName);
     
        Message = New UserMessage;
        Message.Text = "-------- Import completed --------";
        Message.Message();
     
    EndProcedure
    Note that the new procedure part accesses the change registration service and uses the DeleteChangeRecords() method to delete the messages.

    Now you can proceed to reading the data stored in the message.
  6. Update the procedure as shown in listing 24.21.

    Listing 24.21. Reading the message data

    Procedure ReadMessageWithChanges() Export
        
        Directory = TempFilesDir(); 
     
        // Generating file name
        FileName = Directory + ?(Right(Directory,1)= "", "", "") + "Message"
            + TrimAll(Ref.Code) + "_" + TrimAll(ExchangePlans.Branches.ThisNode().Code) 
            + ".xml";
        File = New File(FileName);
        If Not File.Exist() Then
            Return;
        EndIf;
     
        // *** Reading XML document
        // Trying to open the file
        XMLReader = New XMLReader;
        Try
            XMLReader.OpenFile(FileName);
     
        Except
            Message = New UserMessage;
            Message.Text = "Cannot open the data exchange file.";
            Message.Message();
     
            Return;
     
        EndTry;
     
        Message = New UserMessage;
        Message.Text = "-------- Starting import from: " + String(ThisObject) + "---------";
        Message.Message();
        Message = New UserMessage;
        Message.Text = "– Reading file: " + FileName;
        Message.Message();
     
        // Importing data from the file
        // *** Message infrastructure
        ExchangeMessageReader = ExchangePlans.CreateMessageReader();
     
        // Reading the data exchange message header from the XML file
        ExchangeMessageReader.BeginRead(XMLReader);
     
        // The message is not for this node
        If ExchangeMessageReader.Sender <> Ref Then
            Raise "Incorrect node";
        EndIf;
     
        // Deleting change records for the source node
        // *** Change registration service
        ExchangePlans.DeleteChangeRecords(ExchangeMessageReader.Sender,
            ExchangeMessageReader.ReceivedNo); 
        
        // Reading data from the message
        // *** XML serialization
        While CanReadXML(XMLReader) Do
        
        EndDo; 
     
        ExchangeMessageReader.EndRead();
        XMLReader.Close();
        DeleteFiles(FileName);
     
        Message = New UserMessage;
        Message.Text = "-------- Import completed --------";
        Message.Message();
     
    EndProcedure
    The data reading is executed in the loop and XML serialization is involved again. The CanReadXML() global context method gets the next XML data type from the XMLReader object and checks whether a matching 1C:Enterprise data type is available. If it is available, the loop execution continues.

    You have to present XML data as a value that has a type available in 1C:Enterprise. Let us use the ReadXML() global context method for this purpose.
  7. Update the procedure as shown in listing 24.22.

    Listing 24.22. Presenting XML data as a value that has a type

    Procedure ReadMessageWithChanges() Export
        
        Directory = TempFilesDir(); 
     
        // Generating file name
        FileName = Directory + ?(Right(Directory,1)= "", "", "") + "Message"
            + TrimAll(Ref.Code) + "_" + TrimAll(ExchangePlans.Branches.ThisNode().Code) 
            + ".xml";
        File = New File(FileName);
        If Not File.Exist() Then
            Return;
        EndIf;
     
        // *** Reading XML document
        // Trying to open the file
        XMLReader = New XMLReader;
        Try
            XMLReader.OpenFile(FileName);
     
        Except
            Message = New UserMessage;
            Message.Text = "Cannot open the data exchange file.";
            Message.Message();
     
            Return;
     
        EndTry;
     
        Message = New UserMessage;
        Message.Text = "-------- Starting import from: " + String(ThisObject) + "---------";
        Message.Message();
        Message = New UserMessage;
        Message.Text = "– Reading file: " + FileName;
        Message.Message();
     
        // Importing data from the file
        // *** Message infrastructure
        ExchangeMessageReader = ExchangePlans.CreateMessageReader();
     
        // Reading the data exchange message header from the XML file
        ExchangeMessageReader.BeginRead(XMLReader);
     
        // The message is not for this node
        If ExchangeMessageReader.Sender <> Ref Then
            Raise "Incorrect node";
        EndIf;
     
        // Deleting change records for the source node
        // *** Change registration service
        ExchangePlans.DeleteChangeRecords(ExchangeMessageReader.Sender,
            ExchangeMessageReader.ReceivedNo); 
        
        // Reading data from the message
        // *** XML serialization
        While CanReadXML(XMLReader) Do
     
            // Reading next value
            Data = ReadXML(XMLReader);  
         
        EndDo; 
     
        ExchangeMessageReader.EndRead();
        XMLReader.Close();
        DeleteFiles(FileName);
     
        Message = New UserMessage;
        Message.Text = "-------- Import completed --------";
        Message.Message();
     
    EndProcedure
    The ReadXML() method assigns the 1C:Enterprise object that matches the XML data to the Data variable. 

    Next you have to resolve a possible collision.
  8. Update the procedure as shown in listing 24.23.

    Listing 24.23. Resolving possible collisions

    Procedure ReadMessageWithChanges() Export
        
        Directory = TempFilesDir(); 
     
        // Generating file name
        FileName = Directory + ?(Right(Directory,1)= "", "", "") + "Message"
            + TrimAll(Ref.Code) + "_" + TrimAll(ExchangePlans.Branches.ThisNode().Code) 
            + ".xml";
        File = New File(FileName);
        If Not File.Exist() Then
            Return;
        EndIf;
     
        // *** Reading XML document
        // Trying to open the file
        XMLReader = New XMLReader;
        Try
            XMLReader.OpenFile(FileName);
     
        Except
            Message = New UserMessage;
            Message.Text = "Cannot open the data exchange file.";
            Message.Message();
     
            Return;
     
        EndTry;
     
        Message = New UserMessage;
        Message.Text = "-------- Starting import from: " + String(ThisObject) + "---------";
        Message.Message();
        Message = New UserMessage;
        Message.Text = "– Reading file: " + FileName;
        Message.Message();
     
        // Importing data from the file
        // *** Message infrastructure
        ExchangeMessageReader = ExchangePlans.CreateMessageReader();
     
        // Reading the data exchange message header from the XML file
        ExchangeMessageReader.BeginRead(XMLReader);
     
        // The message is not for this node
        If ExchangeMessageReader.Sender <> Ref Then
            Raise "Incorrect node";
        EndIf;
     
        // Deleting change records for the source node
        // *** Change registration service
        ExchangePlans.DeleteChangeRecords(ExchangeMessageReader.Sender,
            ExchangeMessageReader.ReceivedNo); 
        
        // Reading data from the message
        // *** XML serialization
        While CanReadXML(XMLReader) Do
     
            // Reading next value
            Data = ReadXML(XMLReader);
      
            // The change received from a subordinate node is not applied 
            // to the master one if a change record is found
            If Not ExchangeMessageReader.Sender.Main And
                ExchangePlans.IsChangeRecorded(ExchangeMessageReader.Sender, Data) Then
     
                Message = New UserMessage;
                Message.Text = "- Changes rejected";
                Message.Message();
      
                Continue;
            EndIf;
    
     
        EndDo; 
     
        ExchangeMessageReader.EndRead();
        XMLReader.Close();
        DeleteFiles(FileName);
     
        Message = New UserMessage;
        Message.Text = "-------- Import completed --------";
        Message.Message();
     
    EndProcedure
    A possible collision is resolved as follows: the procedure checks whether the sender node is the main node and whether any records regarding changes of this object for this node are available in the database. If the object has been changed in the database and the sender is not a main node, the change record of the received object is rejected. Otherwise the changes of the received object are accepted.

    Now the only thing that is left is writing the received data.
  9. Update the procedure as shown in listing 24.24.

    Listing 24.24. Writing the received data

    Procedure ReadMessageWithChanges() Export
        
        Directory = TempFilesDir(); 
     
        // Generating file name
        FileName = Directory + ?(Right(Directory,1)= "", "", "") + "Message"
            + TrimAll(Ref.Code) + "_" + TrimAll(ExchangePlans.Branches.ThisNode().Code) 
            + ".xml";
        File = New File(FileName);
        If Not File.Exist() Then
            Return;
        EndIf;
     
        // *** Reading XML document
        // Trying to open the file
        XMLReader = New XMLReader;
        Try
            XMLReader.OpenFile(FileName);
     
        Except
            Message = New UserMessage;
            Message.Text = "Cannot open the data exchange file.";
            Message.Message();
     
            Return;
     
        EndTry;
     
        Message = New UserMessage;
        Message.Text = "-------- Starting import from: " + String(ThisObject) + "---------";
        Message.Message();
        Message = New UserMessage;
        Message.Text = "– Reading file: " + FileName;
        Message.Message();
     
        // Importing data from the file
        // *** Message infrastructure
        ExchangeMessageReader = ExchangePlans.CreateMessageReader();
     
        // Reading the data exchange message header from the XML file
        ExchangeMessageReader.BeginRead(XMLReader);
     
        // The message is not for this node
        If ExchangeMessageReader.Sender <> Ref Then
            Raise "Incorrect node";
        EndIf;
     
        // Deleting change records for the source node
        // *** Change registration service
        ExchangePlans.DeleteChangeRecords(ExchangeMessageReader.Sender,
            ExchangeMessageReader.ReceivedNo); 
        
        // Reading data from the message 
        // *** XML serialization
        While CanReadXML(XMLReader) Do
     
            // Reading next value
            Data = ReadXML(XMLReader);
      
            // The change received from a subordinate node is not applied 
            // to the master one if a change record is found
            If Not ExchangeMessageReader.Sender.Main And
                ExchangePlans.IsChangeRecorded(ExchangeMessageReader.Sender, Data) Then
                Message = New UserMessage;
                Message.Text = "- Changes rejected";
                Message.Message();
      
                Continue;
            EndIf; 
     
            // Writing received data
            Data.DataExchange.Sender = ExchangeMessageReader.Sender;
            Data.DataExchange.Load = True;
            Data.Write();
          
        EndDo; 
     
        ExchangeMessageReader.EndRead();
        XMLReader.Close();
        DeleteFiles(FileName);
     
        Message = New UserMessage;
        Message.Text = "-------- Import completed --------";
        Message.Message();
     
    EndProcedure
    Before writing the received object, the procedure records the source node to the data exchange parameters of the object, so that when the object is written to the database, no change registration record is generated for the node it is just received from.

    The procedure also sets the Load property in the data exchange parameters to True. This states that the object is written during the update of the data received during the exchange, which simplifies the object writing procedure for the system because it can avoid some standard checks and prevent modifications of related data that should happen for normal writing.

    This completes the procedure of receiving and processing exchange data.

In Designer mode

To be able to edit the NumberingPrefix constant, you have to create a form.

  1. In the configuration object tree, expand the Common branch, click the Common forms branch, and use the form wizard to create a constants form named GeneralSettings.
  2. In the More: GeneralSettings window that is opened, specify that the form belongs to the Enterprise subsystem (fig. 24.7).

    Lesson 24 (6:10). Data exchange / Universal data exchange / Testing data exchange / In Designer mode
    Fig. 24.7. The More window of the GeneralSettings constants form
  3. In the configuration object editors for the Branches exchange plan and the DataExchange data processor, on the Subsystems tab, specify that they belong to the Enterprise subsystem.
  4. In the Enterprise subsystem command interface, specify that only Administrator has access to the commands that open the exchange plan, the data processor, and the constants form.
  5. Move the Branches command to the top of the Navigation panel.Important group.
  6. In the Actions panel.Create group, for the Branch: create command, enable visibility to the Administrator role only.
  7. In the Actions panel.Tools group, order the commands as follows:
    • General settings
    • Data exchange   
  8. Update the database configuration by pressing F7.
  9. Create a directory for storing the branch infobase.
  10. On the Configuration menu, click Save configuration to file and save the configuration to that directory.
    Lesson 24 (6:10). Data exchange / Universal data exchange / Testing data exchange / In Designer mode
    Fig. 24.8. Enterprise subsystem command interface

In 1C:Enterprise mode

Let us enter the values required for data exchange in the central infobase.

  1. Start 1C:Enterprise in the debug mode.

    First, let us set the numbering prefix.
  2. In the Enterprise section, on the Tools menu, click General settings.
  3. In the Numbering prefix field, enter CI and then click Save and close (fig. 24.9).

    Lesson 24 (6:10). Data exchange / Universal data exchange / Testing data exchange / In 1C:Enterprise mode
    Fig. 24.9. Value of the NumberingPrefix constant

    Then let us open the exchange plan and specify the default node parameters, i.e. the parameters of the current infobase.
  4. In the Enterprise section, in the section commands panel, click Branches.

    You can see that the list of exchange plans contains a single empty row.
  5. Open that row, enter CI for the infobase code and Central infobase for its description (fig. 24.10), then click Save and close.

    Remember that a code serves as a unique node ID across all infobases. Later you will create nodes with the same codes in the branch infobase.

    Lesson 24 (6:10). Data exchange / Universal data exchange / Testing data exchange / In 1C:Enterprise mode 
    Fig. 24.10. Creating an exchange plan node

    Then let us create the branch infobase node.
  6. Click the Create button.

    -OR-

    On the Create menu, click Branch.
  7. Enter BR for the infobase code and Branch for its description (fig. 24.11), then click Save and close.

    Lesson 24 (6:10). Data exchange / Universal data exchange / Testing data exchange / In 1C:Enterprise mode
    Fig. 24.11. Creating an exchange plan node

    Note that the predefined infobase node (Central database) is marked with a special icon in the list of exchange nodes, and the Write changes button is not available for this node.
  8. Click the Branch node and click Write changes.
  9. Open the Data exchange data processor and click Start data exchange.

    The text shown in fig. 24.12 is displayed in the message window.

    Lesson 24 (6:10). Data exchange / Universal data exchange / Testing data exchange / In 1C:Enterprise mode
    Fig. 24.12. Service message window

    So as a result of the data exchange operation the central infobase generated an exchange file containing the changes of all the data it exchanges with the branch.

Running the branch infobase

Let us proceed to the branch infobase.

You have to add a new infobase with an empty configuration to the infobase list. The infobase will be located in the directory that you created earlier in this lesson.

  1. Run 1C:Enterprise.
  2. In the 1C:Enterprise startup window, click Add, click Creating an infobase, and then click Next.
  3. Select Creating an infobase without configuration... and click Next.
  4. Enter the infobase name (for example, Branch infobase), then click Next.
  5. Specify the infobase directory where the saved configuration is located (for example, D:Practical developer guideranch), click Next, and click Ready.
In Designer mode

Let us prepare the branch infobase for the exchange.

  1. Open the branch infobase configuration in Designer mode.
  2. On the Configuration menu, click Open configuration.

    You can see that the list of configuration objects is empty.
  3. On the Configuration menu, click Load configuration from file, select the file that you created earlier (for example, D:Practical developer guideranch1Cv8.cf), and click Open.
  4. When prompted to confirm the configuration update, click Yes.
  5. In the list of configuration changes, click Accept.

    All the configuration objects are now transferred from the central infobase.

    There is one thing that requires special attention. The data exchange objects include the Warehouses catalog that has a predefined item named Main.

    When you create catalogs or other configuration objects that can have predefined items, their Update predefined data property is set to Auto. It means that, once a database is restructured or a table that stores the configuration object data is accessed for the first time, data items linked to predefined data items by name are created or updated.

    In this scenario, once you open the list of warehouses, the platform automatically creates an item in the Warehouses catalog, which is linked to the predefined catalog item named Main. And the catalog item property PredefinedDataName is set to Main. This link can only be changed using 1C:Enterprise script.

    Then, once you load data from the central infobase to the Warehouses catalog, the platform adds another data item linked to the Main predefined item of the Warehouses catalog. This will cause an error because a configuration cannot have two items linked to a single predefined item.

    To avoid this, you have to change the property value.
  6. Open the object editor window of the Warehouses catalog, click the Other tab, and set Update predefined data to Do not update automatically (fig. 24.13).

    Lesson 24 (6:10). Data exchange / Universal data exchange / Testing data exchange / Running the branch infobase / In Designer mode
    Fig. 24.13. Editing the properties of the Warehouse catalog
  7. On the Administration menu, click Users and create a single user: Administrator with Administrator role.

    This is because you need to create users from scratch in each infobase.
In 1C:Enterprise mode

Let us enter the values required for data exchange.

  1. Start 1C:Enterprise in the debug mode.
  2. Set the Numbering prefix to BR (fig. 24.14).

    Lesson 24 (6:10). Data exchange / Universal data exchange / Testing data exchange / Running the branch infobase / In 1C:Enterprise mode
    Fig. 24.14. Editing the constant

    Then let us open the exchange plan and describe the predefined node (the current infobase node).
  3. In the Enterprise section, on the navigation panel, click Branches.

    You can see that the list of exchange plans contains a single empty row.
  4. Open that row, enter BR for the infobase code and Branch for its description (fig. 24.15), then click Save and close.

    Lesson 24 (6:10). Data exchange / Universal data exchange / Testing data exchange / Running the branch infobase / In 1C:Enterprise mode
    Fig. 24.15. Creating an exchange plan node
  5. Create an exchange plan node with CI for the infobase code and Central infobase for its description, and the Main check box selected (fig. 24.16), then click Save and close.

    Lesson 24 (6:10). Data exchange / Universal data exchange / Testing data exchange / Running the branch infobase / In 1C:Enterprise mode
    Fig. 24.16. Creating an exchange plan node
  6. Click the new Central infobase node and then click Write changes.

    To better understand the process, let us check some infobase catalogs.
  7. Open the list of the Customers catalog and ensure that it is empty for now.
  8. Open the list of Warehouses and ensure that it is also empty.

    It does not contain the predefined item because you set its Update predefined data property to Do not update automatically.
  9. Open the Data exchange data processor and click Start data exchange.

    This fills the catalogs with items, and also fills other objects that are included in the exchange. And the message window displays the messages shown in fig. 24.17.

    Lesson 24 (6:10). Data exchange / Universal data exchange / Testing data exchange / Running the branch infobase / In 1C:Enterprise mode
    Fig. 24.17. Exchange plan messages

    Note. To view the list of customers replicated from the central infobase, refresh the list (press F5).

    Now let us test how exchange is processed on the other side.
  10. In the Customers catalog, create a new customer with an arbitrary name.

    Note that the customer code has the BR prefix and the code numbering starts from one.
  11. Return to the Data exchange data processor and click Start data exchange.
  12. Return to the central infobase, perform the data exchange there, and ensure that the customer created in the branch infobase has been transferred to the central one.

Leave a Reply

Your email address will not be published.

 

1C:Enterprise Developer's Community