📜 ⬆️ ⬇️

The experience of automating uneasy correspondence (Completion. Sampling and Export)

In conclusion of the description of my experience in improving the process of managing correspondence, it remains for me to tell only about the samples that all participants in the process deal with. Visual presentation of information is one of the most important components of a successful launch, agree!

Let me remind you that in previous articles the behavior of incoming and outgoing letters was described. The climax under the cut: a lot of text, code and a little animation.

I had to divide all the requirements of the participants of the process for the presentation of information into two unequal groups:

It turned out that the requirements of a clerk were somewhat different from the requirements of all the others. It can be said that the members of each group look at the process from different angles.
If it is important for a case management specialist how the document entered the organization or, on the contrary, how it should be sent to the addressee, then the rest is completely uninteresting.
Based on this, it became clear that although users should see similar registries of incoming and outgoing emails, the list of attributes displayed in registries will be different.
Initially, it seemed to me that it would be necessary to create for each group a separate set of types (this is how sampling is called easla.com ) and to “arrange” access to them using rights, but after understanding, it became clear that in most cases you can do with a few lines of code the form itself.
Yes, yes, in easla.com , all types are described by code, which allows for extraordinary flexibility. For example, it does not cost anything to build a code so that an individual user or group displays objects that correspond to an important criterion for them. Or, for example, add a category to the view, each of which has a separate filter.
All types are configured using the parameters passed to the exec () function. Of course, parameters can be generated programmatically in code.
By the way, in easla.com there is a so-called "Quick Master", which allows you to create kinds of "naklikivaniyami." It helps when creating a new look and is convenient for beginners.
So, I will describe all types, focusing on the features of each.

Incoming documents


The first of the two most important registries of documents in the process. Reflects the complete list of all incoming documents in descending order of date. Thus, in the beginning of the list there are always the newest incoming letters.
$attributes = array( 'crs_management_incoming_receive_regnum'=>array('link'=>'object', 'actions'=>array(), 'header'=>'.  '), ); if (cuser()->inGroup('group_dp') == true) $attributes += array('crs_management_incoming_content'=>array('link'=>'value')); $attributes += array( 'crs_management_incoming_receive_date', 'crs_management_incoming_contragent_regnum'=>array('header'=>'.  '), 'crs_management_incoming_contragent'=>array('link'=>'value'), 'crs_management_incoming_contact'=>array('link'=>'value'), 'crs_management_incoming_subj', 'crs_management_incoming_outgoing'=>array('link'=>'value'), 'crs_management_incoming_contract'=>array('link'=>'value','max'=>3), 'crs_management_incoming_document'=>array('link'=>'value'), 'crs_management_incoming_forwardto'=>array('link'=>'value'), 'status' ); cviewpub()->exec(array( 'object'=>objectdef('crs_management', 'crs_management_incoming'), 'attributes'=>$attributes, 'conditions'=>array( 'permission'=>'2', ), 'sort'=>array( 'crs_management_incoming_receive_date'=>array('default'=>'desc', 'enable'=>true), 'crs_management_incoming_contragent'=>array('enable'=>true) ), 'sorting'=>true, 'pagination'=>array('pagesize'=>10), 'showcreate'=>true )); 

In this form, I had to create a list of attributes in the $ attributes variable only because of the requirement of the clerk. Only she needs to see the content type of each incoming letter.
The first column of the view is configured to display the context menu of the object next to the value of the crs_management_incoming_receive_regnum attribute. It contains not only standard actions, but also actions announced by the administrator.

In addition, an interesting nuance surfaced with the crs_management_incoming_contract attribute. It is plural and stores references to the contracts to which the incoming document belongs. In most cases, the attribute had one, maximum two values ​​and there were no problems with it. However, one of the customers was awarded a large contract with more than twenty extra. agreements. He began to send us letters, referring then to one additional. agreement, then a few, then almost everything. The case management specialist had no choice but to diligently indicate all the extras. agreements referenced by the author of the incoming letter. As a result, the number of attribute values ​​became indecently large, and easla.com diligently displayed them all, stretching the cell to almost the entire screen. In general, everything was decided simply, the max option allows you to limit the number of displayed values ​​by hiding them in ellipsis.

The status of the incoming document is colored by the object itself (see in another part ), the view in this plan is not necessary to customize. Ultimately, it looks like this:


Outgoing documents


The second of the most important registries of documents. And this is the only form in my practice that I have divided into two, delimiting access by roles. In other words, he created two types with the same name, but one is intended for a clerk and the other for all others.
By the way, the matrix of accessibility of species by roles looks like this:

Of course, if you try, you could delineate everything with code, but I made that decision. At that moment it seemed to me the most true.
I will give the code of only one type, which is intended for all:
 cviewpub()->categories = array('',' '); cviewpub()->category = is_null(cviewpub()->category) ? 0 : cviewpub()->category; $attributes = array( 'crs_management_outgoing_regnum'=>array( 'link'=>'object', 'actions'=>array(), 'export'=>array( 'id', 'crs_management_outgoing_regnum', 'crs_management_outgoing_sentdate' ) ), 'crs_management_outgoing_sentdate', 'crs_management_outgoing_performers'=>array('link'=>'value'), 'crs_management_outgoing_contragent'=>array('link'=>'value'), 'crs_management_outgoing_contact'=>array('link'=>'value'), 'crs_management_outgoing_subj', 'crs_management_outgoing_incomingdocs'=>array( 'link'=>'value', 'export'=>array( 'id', 'crs_management_incoming_contragent_regnum', 'crs_management_incoming_contragent_date' ) ), 'crs_management_outgoing_contract'=>array('link'=>'value','max'=>3), 'crs_management_outgoing_content'=>array('link'=>'value'), ); $conditions = array(); switch(cviewpub()->category) { case 0: $attributes[] = 'status'; break; case 1: $conditions += array('status'=>array('crs_management_outgoing_create','crs_management_outgoing_created')); break; } cviewpub()->exec(array( 'object'=>objectdef('crs_management', 'crs_management_outgoing'), 'attributes'=>$attributes, 'conditions'=>$conditions, 'sort'=>array( 'crs_management_outgoing_regdate'=>array('default'=>'desc', 'enable'=>true), 'crs_management_outgoing_contragent'=>array('enable'=>true) ), 'sorting'=>true, 'pagination'=>array('pagesize'=>10), 'showcreate'=>true )); 

In this form, I used several extremely useful features for us easla.com .
First of all, I will pay attention to the category. With their help, easla.com allows you to display a view from different angles, or more simply, several views in one. Although you can filter by status directly in the form, some active users are very strongly asked to create for them a separate view with unsent outgoing letters, to which I suggested to allocate them into a separate category. On this and agreed. A couple of lines of code:
 cviewpub()->categories = array('',' '); cviewpub()->category = is_null(cviewpub()->category) ? 0 : cviewpub()->category; 

They create two categories and choose the first, unless otherwise indicated. The active value of the category is used in switch and forms the condition for selecting the $ conditions objects. It turned out easy and convenient.

In addition, it is interesting to dwell on the export option that was used in the columns crs_management_outgoing_regnum and crs_management_outgoing_incomingdocs. This option allows for exporting data (about export below), instead of, say, one attribute value crs_management_outgoing_regnum, to unload attribute values:

In our case, this feature has been very useful in preparing reports that needed to be generated based on the exported data.
')

Outgoing and incoming for the counterparty


In easla.com there is a unique opportunity to create views not in the context of a process , but in the context of an object . Such views are not displayed in the main menu, but are displayed in the tabs of the object that will use them. The smart system will find the connections between the active object and the sample objects!
I needed to display two tabs (views) in the form of the counterpart: Outgoing for the counterparty and Inbox from the counterparty . They allowed to visualize the entire correspondence with the selected counterparty. A very useful feature for the main project engineer. Again, you can make similar samples in the above described types, but it is more convenient.
In the “Counterparty” object I needed to add the following lines:
 $objectTabs = array('crm_management_contact'); if (corganization()->hasViewpub('crs_management_all_incoming_by_contragent')) $objectTabs[] = 'crs_management_all_incoming_by_contragent'; if (corganization()->hasViewpub('crs_management_all_outgoing_by_contragent')) $objectTabs[] = 'crs_management_all_outgoing_by_contragent'; cobjectref()->objectTabs = $objectTabs; 

The objectTabs property must contain codes or names of the species whose bookmarks should appear on the object. The species code for the object is the following:
Outgoing for counterparty
 $attributes = array( 'crs_management_outgoing_regnum'=>array('link'=>'object'), 'crs_management_outgoing_regdate', 'crs_management_outgoing_contact'=>array('link'=>'value'), 'crs_management_outgoing_subj', 'crs_management_outgoing_incomingdocs'=>array('link'=>'value'), 'crs_management_outgoing_contract'=>array('link'=>'value','max'=>3), 'crs_management_outgoing_document'=>array('link'=>'value'), 'status'=>array('actions'=>array()) ); cviewpub()->exec(array( 'object'=>objectdef('crs_management', 'crs_management_outgoing'), 'attributes'=>$attributes, 'sort'=>array( 'crs_management_outgoing_regdate'=>array('default'=>'desc', 'enable'=>true) ), 'pagination'=>array('pagesize'=>10), )); 


Incoming from the counterparty
 cviewpub()->exec(array( 'object'=>objectdef('crs_management', 'crs_management_incoming'), 'attributes'=>array( 'crs_management_incoming_receive_regnum'=>array('link'=>'object'), 'crs_management_incoming_receive_date', 'crs_management_incoming_contragent_regnum', 'crs_management_incoming_contact'=>array('link'=>'value'), 'crs_management_incoming_subj', 'crs_management_incoming_content', 'crs_management_incoming_contract'=>array('link'=>'value','max'=>3), 'crs_management_incoming_document'=>array('link'=>'value'), 'status'=>array('actions'=>array()) ), 'conditions'=>array( 'permission'=>'2', ), 'sort'=>array( 'crs_management_incoming_receive_date'=>array('default'=>'desc', 'enable'=>true) ), 'sorting'=>true, 'pagination'=>array('pagesize'=>10), )); 


As a result, the form of the counterparty began to look like this:

I remember that in elma I did not manage to achieve such a result.

My inbox


An auxiliary view that displays a list of all incoming documents addressed to the active user. Especially convenient to the chief engineer and the GUIs.
 cviewpub()->categories = array('',' ',' ',''); cviewpub()->category = is_null(cviewpub()->category) ? 0 : cviewpub()->category; $attributes = array( 'crs_management_incoming_receive_regnum'=>array('link'=>'object', 'actions'=>array()), 'crs_management_incoming_receive_date', 'crs_management_incoming_contragent'=>array('link'=>'value'), 'crs_management_incoming_contact'=>array('link'=>'value'), 'crs_management_incoming_subj', 'crs_management_incoming_contract'=>array('link'=>'value','max'=>3), 'crs_management_incoming_document'=>array('link'=>'value'), ); $conditions = array('crs_management_incoming_forwardto'=>cuser()->id, 'permission'=>'2'); switch(cviewpub()->category) { case 0: $attributes[] = 'status'; break; case 1: $conditions += array('status'=>array('crs_management_incoming_create','crs_management_incoming_created','crs_management_incoming_handed')); break; case 2: $conditions += array('status'=>array('crs_management_incoming_exec')); break; case 3: $conditions += array('status'=>array('crs_management_incoming_ok')); break; } cviewpub()->exec(array( 'object'=>objectdef('crs_management', 'crs_management_incoming'), 'attributes'=>$attributes, 'conditions'=>$conditions, 'sort'=>array( 'crs_management_incoming_receive_date'=>array('default'=>'desc', 'enable'=>true) ), 'sorting'=>true, 'pagination'=>array('pagesize'=>10), 'showcreate'=>true )); 

As can be seen from the source code, in the form there are also several categories for quick filtering of letters. Otherwise, it is not remarkable.


My outgoing


An auxiliary view that displays a complete list of letters in the development of which an active user participated.
 $attributes = array( 'crs_management_outgoing_regnum'=>array('link'=>'object', 'actions'=>array()), 'crs_management_outgoing_regdate', 'crs_management_outgoing_performers'=>array('link'=>'value'), 'crs_management_outgoing_contragent'=>array('link'=>'value'), 'crs_management_outgoing_contact'=>array('link'=>'value'), 'crs_management_outgoing_subj', 'crs_management_outgoing_incomingdocs'=>array('link'=>'value'), 'crs_management_outgoing_contract'=>array('link'=>'value','max'=>3), 'crs_management_outgoing_document'=>array('link'=>'value'), 'status' ); $conditions = array('crs_management_outgoing_performers'=>cuser()->id); cviewpub()->exec(array( 'object'=>objectdef('crs_management', 'crs_management_outgoing'), 'attributes'=>$attributes, 'conditions'=>$conditions, 'sort'=>array( 'crs_management_outgoing_regdate'=>array('default'=>'desc', 'enable'=>true), 'crs_management_outgoing_contragent'=>array('enable'=>true) ), 'sorting'=>true, 'pagination'=>array('pagesize'=>10), 'showcreate'=>true )); 

Many users start searching for their letters from it.

Export


In easla.com , the export is implemented in Microsoft Excel (xlsx) and Microsoft Excel XML (xml) format , which allows you to upload document registries and process them in the desired form.

When exporting, the attribute values ​​specified in the view settings are unloaded. In other words, the view is unloaded just as it is displayed.
But, we needed to unload a view that contained all incoming and outgoing reciprocal for transmission to the customer, a kind of register confirming the fact of response to each letter by both parties, and in it it was necessary to unload the letter designation, date of dispatch and turn it into a hyperlink. Add the appropriate attributes to the view, moveton. The user only needs a description of the object, why would he need two extra attributes. You can create a separate view with the necessary attributes, but it will be visible in the main menu all the time, and it will only be accessed occasionally. Again ugly.
It turned out that you can specify the export parameter in the column parameters (see the example above ), and specify the list of attributes that should be unloaded during the export instead of the displayed attribute. Insanely comfortable!
In general, with the help of the export parameter, I supplemented all types with the necessary attributes for export and got what I needed.

Unloading letters in the registry


The most interesting from the point of view of implementation is to upload the register of letters from easla.com and then unload the letters themselves. It is impossible to do this with system tools, only the registry is downloaded, but not files, so I had to write a macro.
Write a macro instructed his colleague, he is in this master. The algorithm is pretty simple. Go through the unloaded registry, access each object in easla.com (you can use SOAP , COM or .NET ) at the identifier specified in the registry and download the letter and application files to a separate folder. In the registry, replace the letter designation with a hyperlink to the folder with uploaded files.
Here is the registry in Microsoft Excel after unloading:

"Inciting" him:
Add-on upload files
 using System; using System.Collections.Generic; using System.Linq; using System.Text; using Excel = Microsoft.Office.Interop.Excel; using Office = Microsoft.Office.Core; using TNGP_Office_Helper; using System.Windows.Forms; using System.IO; using EaslaHelper; namespace TNGP_ExcelAddIn_Specification { public class Letters { const string SHEET_NAME = "Export"; const int COLUMNS_COUN = 12; Excel.Workbook wb = null; public void RunForestRun() { ReadData(); } public void ReadData() { wb = Exl.ActiveWb; if (wb == null) return; var exportSheet = wb.GetSheet(SHEET_NAME); if (exportSheet == null) return; string path = string.Empty; var fbd = new FolderBrowserDialog(); fbd.Description = "   ."; if (fbd.ShowDialog() == DialogResult.OK) path = fbd.SelectedPath; else return; int index = 1; if (wb.Path != path) wb.SaveAs(Path.Combine(path, wb.Name)); var rowsCount = ExcelHelper.CountDataRows(exportSheet, 1, 1, COLUMNS_COUN); for (int iRow = rowsCount; iRow >= 2; iRow--) { string idOut = exportSheet.Range["A" + iRow].Text; string numberOut = exportSheet.Range["B" + iRow].Text; string pathOut = Path.Combine(path, Helper.ReplaceChars(numberOut)); if (!Directory.Exists(pathOut)) Directory.CreateDirectory(pathOut); EaslaHelperClass.DLDocument("Out", "OutDocAttach", idOut, pathOut); index++; exportSheet.Hyperlinks.Add(Anchor: exportSheet.Range["B" + iRow], Address: Helper.ReplaceChars(numberOut), TextToDisplay: numberOut); string idIn = exportSheet.Range["H" + iRow].Text; if (!string.IsNullOrEmpty(idIn)) { string numberIn = exportSheet.Range["I" + iRow].Text; string dateIn = exportSheet.Range["J" + iRow].Text; string[] arrId = idIn.Split(';'); string[] arrNumber = numberIn.Split(';'); string[] arrDate = dateIn.Split(';'); if (arrId.Length > 1) { var RngToCopy = exportSheet.Rows[string.Format("{0}:{0}", iRow)]; for (int j = 0; j < arrId.Length - 1; j++) { var RngToInsert = exportSheet.Range["A" + iRow].EntireRow; RngToInsert.Insert(Excel.XlInsertShiftDirection.xlShiftDown, RngToCopy.Copy(Type.Missing)); } } for (int i = 0; i < arrId.Length; i++) { exportSheet.Range["H" + (iRow + i)].Value2 = arrId[i]; exportSheet.Range["J" + (iRow + i)].Value2 = arrDate[i]; var numberInNew = Helper.ReplaceChars(arrNumber[i]); exportSheet.Hyperlinks.Add(Anchor: exportSheet.Range["I" + (iRow + i)], Address: numberInNew, TextToDisplay: arrNumber[i]); string pathIn = Path.Combine(path, numberInNew); if (!Directory.Exists(pathIn)) Directory.CreateDirectory(pathIn); EaslaHelperClass.DLDocument("In", "InDocAttach", arrId[i], pathIn); index++; } } double percent = index * 100 / (rowsCount * 2); ExcelHelper.SetStatusBarPercent(Exl.ExlApp, Helper.PercentProgressBar(percent), " "); } exportSheet.Range["A:A"].EntireColumn.Hidden = true; exportSheet.Range["H:H"].EntireColumn.Hidden = true; wb.Save(); MessageBox.Show(" !"); } } } 


Secondary functions
  public static DWReturnType DLDocument(string objdef, string attrs, string id, string path, string filename = "") { string objectdef = string.Empty; string[] attrrefs = null; switch (objdef) { case "Out": objectdef = "crs_management_outgoing"; break; case "In": objectdef = "crs_management_incoming"; break; } switch (attrs) { case "InDocAttach": attrrefs = new string[] { "crs_management_incoming_document", "crs_management_incoming_attachments" }; break; case "InDoc": attrrefs = new string[] { "crs_management_incoming_document" }; break; case "OutDocAttach": attrrefs = new string[] { "crs_management_outgoing_document", "crs_management_outgoing_attachments" }; break; case "OutDoc": attrrefs = new string[] { "crs_management_outgoing_document" }; break; } var process = EaslaApp.getProcess("crs_management"); var object_def = EaslaApp.getObjectdef(process, objectdef, 0); var objectrefs = GetObjectRefs(id, object_def, attrrefs); if (objectrefs == null) return DWReturnType.Error; if (objectrefs.Length == 0) return DWReturnType.LetterNotFound; if (objectrefs.Length > 1) return DWReturnType.FoundManyLetters; return DownloadFile(filename, path, objectrefs[0].attributerefs); } public static DWReturnType DownloadFile(string filename, string path, Easla.AttributerefSimpleSoap[] attrs) { DWReturnType result = DWReturnType.FileNotFound; foreach (var attr in attrs) { if (attr.values != null) { var files = attr.values.OfType<Easla.FileSimpleSoap>(); var selectfiles = files.Where(f => f.isdeleted == "0" & f.vid == "0"); foreach (var fss in selectfiles) { string filePath = Path.Combine(path, fss.nowname); if (!string.IsNullOrEmpty(filename)) { string ext = Path.GetExtension(fss.nowname); filePath = Path.Combine(path, filename + ext); } EaslaApp.DownloadFile(fss.id, filePath); result = DWReturnType.Ok; } } } return result; } 


The result looks like this:

I think it’s not even worth convincing anyone of the economic effect of such a decision. Time, which means the money spent on the preparation of the report and the unloading of letters decreased by two, then by three orders of magnitude! A couple of weeks ago, one of the GUI assistants unloaded in this way more than a hundred or even two letters. All correspondence with the customer for a certain period. Launched unloading in the evening and left for the night. In the morning I got the finished result! Manually, he would have a couple of weeks, if not more, collected all the files.

Results


Despite the fact that I needed to write three voluminous articles to describe the process, I can safely say that creating a process in easla.com is simple and easy. In fact, the entire process of managing correspondence took 2-3 part-time days, because I was distracted by current affairs. A serious and attentive approach required only the action “Send by email. mail ”in the“ Outgoing Document ”object (see previous article ). Otherwise, the code of objects and attributes is simple, repetitive and easy to read.
I am convinced that any other process administrator at easla.com can easily read the process code and will understand it in two accounts.
With full confidence, I can insist that easla.com is an excellent electronic document management system, ready to fight back many similarly expensive commercial products on the Russian market!
PS I would like to draw your attention to the fact that the process described is not available for borrowing only because of the presence of confidential data in it. Its full analogue. Correspondence published on the site for general use without any restrictions! There will be questions - write to support@easla.com - you will be answered!

Source: https://habr.com/ru/post/282347/


All Articles