📜 ⬆️ ⬇️

The experience of automating difficult correspondence (Part 1. Incoming)

The experience of automating difficult correspondence (Part 1. Incoming)
I work as the head of the information technology department in the project organization in Tyumen. A little more than a year ago, I was given the task to improve the process of managing official correspondence working at elma at that time.

Instead, elma was chosen easla.com system. Under the cut a lot of text and code.

First of all, a few general phrases about the process itself. To the uninitiated, it seems simple and even unworthy of automation. Actually, initially I thought the same. However, when I listened to the groans of a paperworker - the main participant in the process, I listened to the demands of the chief engineer - the main consumer of the process, I realized that everything is not as simple as it seems. It turned out that the timely sent letter can bring the organization six, and sometimes seven-figure profit!
At one time, the process was automated using the elma system, but within one year of operation, it became clear that the system needed to be changed to something more flexible and responsive.
So, the requirements for the new process were as follows (short list):

When thinking about the process, it became clear that by itself it could not exist. It will take several related processes that will become suppliers of additional information:
Customers - the process of managing contractors and contacts. All letters are attached to the relevant counterparties and contacts.
Contracts - the process of managing contracts. All letters, in most cases, are tied to a specific contract or even to several.
Tasks - the process of managing orders (tasks), allows you to track the execution of orders for each document. Very serious process, it is better to tell about it separately. He took over the tracking of the preparation of the response to the incoming letter.
At that time I was already registered in the easla.com system and successfully used it to manage other, less significant processes of the organization. Therefore, without inventing anything special, I created a new process in the system. He called it "Correspondence", afterwards in the process created the object "Incoming Document" and proceeded to fill it with attributes.

Attributes


The “Incoming Document” object has a whole bunch of attributes, each one needs to be told separately.

Recipient's registration number

The usual sequence number of the incoming document. Numbering starts anew each year. It was not possible to use a simple numerator to assign a sequence number, it is not able to reset the value every year, so the function was declared in the script “Before the object was initialized”:
function updateDocumentRegNum() { $year = date_format(currentDateTime(), 'Y'); $num = selectAggregateAll( 'max', 'crs_management', 'crs_management_incoming', 'crs_management_incoming_receive_regnum', array('crs_management_incoming_receive_date'=>array('between', $year.'-01-01', $year.'-12-31'),) ); return $num + 1; } 

The updateDocumentRegNum function is called from the “At Initialization” attribute script:
 cattributeref()->value = cobjectref()->updateDocumentRegNum(); 

“When an attribute is changed”, the function updateDocumentFileName is called, which is responsible for updating the file name of the cover letter (not applications), but about it later.
 cobjectref()->updateDocumentFileName(); 

')
Type of shipment

Classifier that determines the method of sending the incoming document to our address. It can take the following values:

In easla.com it is possible to store constants in hierarchically ordered classifiers. Very convenient, avoids the need to store arrays or constants in the code describing an object or attribute.
The “At Initialization” attribute script returns a list of nested classifiers, processes them and assigns a list of valid attribute values:
 $src_classificators = classificatorChilds('crs_method'); $end_classificators = array(); foreach($src_classificators as $c) $end_classificators += array($c['id']=>$c['name']); if (count($end_classificators) > 0) { cobjectref()->attributeref('crs_management_incoming_method')->values=$end_classificators; cobjectref()->attributeref('crs_management_incoming_method')->value = key($end_classificators); } 

Personally, I liked that the list of acceptable values ​​can be formed so simply. For example, in elma, this was much more difficult.

Counterparty

Link to the “Counterparty” object from the “Customers” process. In order to obtain a list of available counterparties, the auxiliary function prepareIncomingContragents was declared in the “Before object initialization” object script:
 function prepareIncomingContragents() { $src_contragents = selectAll( 'crm_management', 'crm_management_contragent' ); $end_contragents = array(); foreach ($src_contragents as $s) $end_contragents += array($s['id'] => $s['description']); asort($end_contragents); return $end_contragents; } 

The selectAll function selects all the objects “crm_management_contragent” from the process “crm_management” without any conditions, processes and returns as the result of the function.
Attribute initialization occurs in the “After object initialization” object script:
 cobjectref()->attributeref('crs_management_incoming_contragent')->values = prepareIncomingContragents(); 

I pay attention that not in the attribute itself, but in the object. So, too, can be. Sometimes just such a decision can make a big difference.
The choice of the counterparty by the user should lead to a change in the list of available contacts and contracts. The list of available contacts and contracts is formed in the “At Change” attribute script:
 if (!empty(cattributeref()->value)) { $contacts = cobjectref()->prepareIncomingContacts(cattributeref()->value); cobjectref()->attributeref('crs_management_incoming_contact')->values = $contacts; cobjectref()->attributeref('crs_management_incoming_performers')->values = $contacts; $contracts = cobjectref()->prepareContracts(cattributeref()->value); if (empty($contracts)) $contracts = cobjectref()->prepareContracts(); cobjectref()->attributeref('crs_management_incoming_contract')->values = $contracts; } 

The auxiliary functions declared in the “At Initialization” object script are used:
 function prepareIncomingContacts($contragent = null) { if (empty($contragent)) $src_contacts = selectAll( 'crm_management', 'crm_management_contact', array('crm_management_contact_contragent.description'), null // array('with'=>'title') ); else $src_contacts = selectAll( 'crm_management', 'crm_management_contact', array('crm_management_contact_contragent.description'), array('crm_management_contact_contragent'=>$contragent) ); $end_contacts = array(); $processed_contact = array(); $processed_description = array(); foreach ($src_contacts as $s) { $e = array_search($s['description'], $processed_description); if($e === false) { $processed_contact += array($s['id'] => $s); $processed_description += array($s['id'] => $s['description']); $end_contacts += array($s['id'] => $s['description']); } else { $end_contacts[$e] = $processed_contact[$e]['description'].' ['.trim($processed_contact[$e]['crm_management_contact_contragent.description']).']'; $end_contacts += array($s['id'] => $s['description'].' ['.trim($s['crm_management_contact_contragent.description']).']'); } } unset($processed_contact,$processed_description); asort($end_contacts); return $end_contacts; } 

The function is a good example of why a script description of behavior is better than a “clicked” mouse. In this case, the trick is that the list of contacts can meet full namesake, i.e. surname, name and patronymic can be completely matched. To distinguish one from another in the generated list will be unrealistic. Therefore, the function scans the entire list of contacts, finds the same and assigns to them the names of the organizations in which they work. Of course, theoretically, in the same organization, full namesakes can also work, but so far this situation has never arisen.
In addition, an auxiliary function is used to generate a list of contracts:
 function prepareContracts($contragent = null) { if (empty($contragent)) $src_contracts = selectAll( 'agr_management', 'agr_management_contract' ); else $src_contracts = selectAll( 'agr_management', 'agr_management_contract', array(), array('agr_management_contract_contragent'=>$contragent) ); $end_contracts = array(); foreach ($src_contracts as $s) $end_contracts += array($s['id'] => $s['description']); asort($end_contracts); return $end_contracts; } 

There is only one trick in it, in order not to limit the user in choosing contracts, unless the counterparty is specified in the letter, all contracts are available, otherwise, only contracts with the specified counterparty. This solution reduces the probability of error to almost zero.

Contact

Reference to the object "Contact" in the process "Customers". All the auxiliary functions necessary for the initialization of attribute values ​​are described above, therefore, it remains only to add that the attribute is initialized in the “After object initialization” object script (following the initialization of the list of contractors):
 $contacts = prepareIncomingContacts(); cobjectref()->attributeref('crs_management_incoming_contact')->values = $contacts; 

By the way, the user, when filling in the form of an incoming letter, may not indicate the counterparty, but immediately indicate the desired contact. The system will determine its counterparty and substitute the value. It was possible to implement such a trick in the “At Change” attribute script:
 if (empty(cobjectref()->attributeref('crs_management_incoming_contact')->value)) return; if (empty(cobjectref()->attributeref('crs_management_incoming_contragent')->value)) { $contact = select(cobjectref()->attributeref('crs_management_incoming_contact')->value); if (empty($contact)) return; $contragent_id = $contact->attributeref('crm_management_contact_contragent')->value; cobjectref()->attributeref('crs_management_incoming_contragent')->value = $contragent_id; cobjectref()->attributeref('crs_management_incoming_performers')->values = cobjectref()->prepareIncomingContacts($contragent_id); $contracts = cobjectref()->prepareContracts($contragent_id); if (empty($contracts)) $contracts = cobjectref()->prepareContracts(); cobjectref()->attributeref('crs_management_incoming_contract')->values = $contracts; } 

In addition to the definition of the counterparty, the list of available contracts is updated.

Performers

Reference to the object "Contact" in the process "Customers". Designed to store information about who exactly developed the letter on the side of the customer. This is such a short postscript at the end of the letter, they say he performed such and such. By the way, there are several performers, so the attribute is multiple. Allows you to save not one, but a whole list of performers. The attribute is also initialized in the “After object initialization” object script:
 cobjectref()->attributeref('crs_management_incoming_performers')->values = $contacts; 


departure date

Actually, it stores the date of departure of the incoming letter, which the clerk takes from the document. It is important to know when the letter was sent and when it was received. Often these dates are the same in our fast 21st century, so for ease of filling, the attribute is assigned the current date and time in the “At Initialization” script:
 cobjectref()->attributeref('crs_management_incoming_contragent_date')->value = currentDateTime(); 


Sender's registration number

As the name of the attribute suggests, it is intended to store the registration number of the input. letters assigned by the sender. An important attribute, because Both parties are often guided when searching for a letter by this particular registration number, and not by the recipient's registration number described above. However, it also happens that reg. the sender's number is missing altogether, so initially the attribute is assigned “b / n” in the “At Initialization” script:
 cattributeref()->value = '/'; 


date of receiving

Obviously, stores the date of receipt of the incoming letter. As well as the date of departure, the current date and time in the "At Initialization" script is assigned:
 cobjectref()->attributeref('crs_management_incoming_receive_date')->value = currentDateTime(); 

But “When changing” the attribute value, the file name is updated using the updateDocumentFileName function:
 cobjectref()->updateDocumentFileName(); 


Date of receipt of the original

The next date is often left blank. The attribute is intended to store the date of receipt of the original letter, which was received in advance in another way.

Notify the receipt of the original

At the initial development of the object of this attribute was not. He appeared a few weeks after the start of operation. The meaning of its existence is to know whether it is necessary to notify interested parties about the arrival of the original incoming letter or not. In addition, if the alert was sent, the attribute changes the value to Notified.
In easla.com, a list of predefined attribute values ​​can be formed even by a simple array. Just like that, I added a list with three possible values ​​in the “At Initialization” attribute script:
 cattributeref()->values = array('','',''); if (empty(cattributeref()->value)) cattributeref()->value = 0; 


Theme

Actually, the subject of the letter. With it, everything is clear, the usual text field in which the clerk writes the subject of the letter taken from the document. Attribute has no special behavior, only indicated that it can be multi-line.

Type of Content

Classifier, allowing to determine the type of content of the letter. The list of possible values ​​is stored as well as the type of shipment. In total, we use about 25 types of content, here are some of them:

The list of acceptable values ​​is formed similarly to the “Type of shipment” attribute:
 $src_classificators = classificatorChilds('crs_content'); $end_classificators = array(); foreach($src_classificators as $c) { $end_classificators += array($c['id']=>$c['name']); if ($c['code'] == 'crs_content_other') $default = $c['id']; } if (count($end_classificators) > 0) { cobjectref()->attributeref('crs_management_incoming_content')->values=$end_classificators; cobjectref()->attributeref('crs_management_incoming_content')->value = isset($default) ? $default : $c['id']; } 


To whom

User (employee) of the organization to which the official letter is sent. In our case, the letters come only to senior management and chief project engineers (GUIs). Thus, I needed to limit the list of available users. The values ​​are initialized again in the “At Initialization” attribute script:
 $src_users = corganization()->allUsersByGroups(array( 'group_general_manager', 'group_general_engineer', 'group_general_manager_operations', 'group_gip', 'group_general_manager_economics' ), null); $end_users = array(); foreach($src_users as $u) if ($u['islocked'] == 0) $end_users += array($u['id']=>$u['description']); asort($end_users); cobjectref()->attributeref('crs_management_incoming_to')->values = $end_users; 

The clever function allUsersByGroups can return an array of users belonging to specified groups. She is able to exclude users from an array. In general, in my case, without complex filters, I got all the necessary users, processed and assigned attribute values.
A letter may be forwarded to another employee, but just in case, the “When a Change” script initially installs the same employee:
 if (empty(cobjectref()->attributeref('crs_management_incoming_forwardto')->value)) { cobjectref()->attributeref('crs_management_incoming_forwardto')->value = cobjectref()->attributeref('crs_management_incoming_to')->value; } 


Redirect

Probably, in half of the cases, the letter addressed to the general director should actually be sent for consideration not to him, but to his deputy or chief engineer, since carry a purely technical content. Therefore, the incoming letter at registration is sent to the employee responsible for solving the issues described in the letter.
The list of available users is formed in the “At Initialization” attribute script:
 $src_users = corganization()->allUsers(); $end_users = array(); foreach($src_users as $u) if ($u['islocked'] == 0) $end_users += array($u['id']=>$u['description']); asort($end_users); cobjectref()->attributeref('crs_management_incoming_forwardto')->values = $end_users; 


In response to the outgoing

Link to the outgoing document of the same process. A very important attribute, because allows you to chain all incoming and outgoing letters and track it if necessary. In the script “Before the object is initialized,” the auxiliary function prepareOutgoings is written, which generates a list of all outgoing emails:
 function prepareOutgoings() { $src_documents = selectAll( 'crs_management', 'crs_management_outgoing' ); $end_documents = array(); foreach ($src_documents as $d) $end_documents += array($d['id'] => $d['description']); return $end_documents; } 

It is called “At initialization” attribute:
 $outgoings = cobjectref()->prepareOutgoings(); $outgoings = array_reverse($outgoings, true); asort($outgoings); cattributeref()->values = $outgoings; 

The attribute is multiple, so an incoming letter can be registered as a reply to several of our letters at once.

Contract

Reference to the object "Contract" in the process "Contracts". Another important attribute that allows to classify letters under contracts, which simplifies the search and formation of reports in the future. The attribute is multiple, which makes it possible to attribute an incoming letter to several contracts at once. This happens quite often.
Auxiliary functions for initializing attribute values ​​have been described above, so I will only indicate that its values ​​are formed in the “After an object is initialized” script, following the initialization of executors:
 cobjectref()->attributeref('crs_management_incoming_contract')->values = prepareContracts(); 


Document

It is in this attribute that the file of the incoming document is stored. The attribute is not multiple, which forces you to store only one file in it, and in no case, not several.
After uploading the file to the form, it is automatically renamed using the “When Modified” attribute script:
cobjectref () -> updateDocumentFileName ();
Renaming uses the helper function updateDocumentFileName described in the “Before an object is initialized” script:
 function updateDocumentFileName() { $desc = calcIncomingDesc(); if (empty($desc)) return; $files = cobjectref()->attributeref('crs_management_incoming_document')->availableFiles(); foreach ($files as $f) { $nowname = sprintf( '%s.%s', $desc, pathinfo($f->nowname, PATHINFO_EXTENSION) ); if (strcmp($nowname, $f->nowname) == 0) continue; $f->nowname = $nowname; $f->save(); } } 

The new file name is formed from the object description, which, in turn, is formed using the calcIncomingDesc function (about it later).

Applications

Along with the official incoming letter, application files can also be received. There may be many of them and they may be of various formats, therefore the attribute is multiple and does not limit the format of the downloaded files.
By the way, I will mention that the ability to store files in different attributes independently became one of the main reasons for using easla.com to manage correspondence. I have not yet encountered such a system that would handle files as well as ordinary attributes.

The sender is notified of the transfer letter

An interesting attribute that reflects the fact of sending a notification to the sender that his letter was submitted for consideration. It was added at the request of the clerk who complained that she regularly had to answer calls from the customer with a request to indicate whether the letter reached the addressee or not. As a result, the attribute and automatic sending of the corresponding notification to the sender appeared.
An attribute can take one of three values ​​and initially it is, of course, equal to "No":
 cattributeref()->values = arraY('','',''); cattributeref()->value = 0; 

In that case, if the send notification failed, for example, no email was specified. contact address, then after the transfer of ix. the letter of consideration attribute will take the value "Impossible". But this is rare. In most cases, the notification is successfully sent and the attribute is set to "Yes".
I remember the reaction of some customers to the appearance of such a notice. They were just thrilled. I did not even think that such a trifle could cause such a violent positive reaction!

An object


One of the interesting requirements was to “color” the status of incoming letters to increase visibility.


In easla.com, it is enough to add this to the “After object initialization” script, this code:
 switch (cobjectref()->status->code) { case 'crs_management_incoming_created': cobjectref()->status->state = 1; break; case 'crs_management_incoming_handed': cobjectref()->status->state = 2; break; case 'crs_management_incoming_exec': cobjectref()->status->state = 3; break; case 'crs_management_incoming_ok': cobjectref()->status->state = 4; break; } 

You can assign up to 9 colors. All colors of the rainbow are from 1 to 7. White - 8. Black - 9.
By the way, in order for an object to receive a description (initially it is empty), an auxiliary function was written in the script “Before an object is initialized”:
 function calcIncomingDesc() { if (empty(cobjectref()->attributeref('crs_management_incoming_receive_date')->value)) return; if (empty(cobjectref()->attributeref('crs_management_incoming_receive_regnum')->value)) return; $d = date_create(cobjectref()->attributeref('crs_management_incoming_receive_date')->value); return sprintf( '%s  %s', cobjectref()->attributeref('crs_management_incoming_receive_regnum')->value, localeFormatDate($d) ); } 

Which was called in the “Before saving an object” script:
 cobjectref()->description = calcIncomingDesc(); 

In addition, before saving the object, the status is changed and the registration number assigned at the time of creation is changed. I really missed this opportunity in elma, it first saves the object, and then ... then it's too late!
 if (cobjectref()->status->code == 'crs_management_incoming_create') { cobjectref()->status = 'crs_management_incoming_created'; cobjectref()->flags = 1; } else cobjectref()->flags = 0; if (cobjectref()->isNewRecord && !empty(cobjectref()->attributeref('crs_management_incoming_receive_regnum')->value) && !empty(cobjectref()->attributeref('crs_management_incoming_contragent')->value)) { $year = date_format(date_create(cobjectref()->attributeref('crs_management_incoming_receive_date')->value), 'Y'); $conditions = array( 'crs_management_incoming_receive_regnum'=>cobjectref()->attributeref('crs_management_incoming_receive_regnum')->value, 'crs_management_incoming_contragent'=>array('id',cobjectref()->attributeref('crs_management_incoming_contragent')->value), 'crs_management_incoming_receive_date'=>array('between', $year.'-01-01', $year.'-12-31'), ); $exist = selectCountAll('crs_management','crs_management_incoming',$conditions); if ($exist) throw new Exception('  , ..     .  '.cobjectref()->attributeref('crs_management_incoming_receive_regnum')->value.'   '.$year.' !'); } if (cobjectref()->isNewRecord && !empty(cobjectref()->attributeref('crs_management_incoming_contragent_regnum')->value) && !empty(cobjectref()->attributeref('crs_management_incoming_contragent_date')->value) && cobjectref()->attributeref('crs_management_incoming_contragent_regnum')->value != '/') { $year = date_format(date_create(cobjectref()->attributeref('crs_management_incoming_contragent_date')->value), 'Y'); $conditions = array( 'crs_management_incoming_contragent_regnum'=>array('strict',cobjectref()->attributeref('crs_management_incoming_contragent_regnum')->value), 'crs_management_incoming_contragent'=>array('id',cobjectref()->attributeref('crs_management_incoming_contragent')->value), 'crs_management_incoming_contragent_date'=>array('between', $year.'-01-01', $year.'-12-31'), ); $exist = selectCountAll('crs_management','crs_management_incoming',$conditions); if ($exist) throw new Exception('  , ..     .   '.cobjectref()->attributeref('crs_management_incoming_contragent_regnum')->value.'   '.$year.' !'); } 

In the end, this is the form of the incoming document:


Statuses


Having dealt with the object and its attributes, I was puzzled by statuses. In easla.com there are three types of statuses: initial, normal and final. The initial object is assigned at the time of creation, i.e. when the object has not yet been saved and the form for creating the object is open. The final one blocks any attempts to change the object, although it can be circumvented by using actions.
After a short discussion, it came together on the following statuses of the incoming document:
Registration - initial status
Registered - assigned immediately after registration
Sent to addressee - assigned after sending a "forwarding" letter demanding to consider the document
Considered - assigned after assigning tasks to prepare an answer (tasks are executed in the adjacent “Tasks” process)
Canceled - assigned if the letter needs to be canceled.
At this stage it was already possible to breathe easier. It was necessary to show the form to the clerk, as she was the one who had to work with her most of the time. We created one object with it. Have a look what happened. In general, I received from her a positive response and a bunch of wishes.

Actions


In easla.com, an object can be transferred from one status to another only by action. An action is a button below the object form that executes the script in the context of the object.

For consideration

Translates the object from the “Registered” status to “Transferred to the addressee” and sends a letter to the “redirector” with a request to consider the incoming document. The action script turned out simple:
 if (empty(cobjectref()->attributeref('crs_management_incoming_forwardto')->value)) throw new Exception("  !"); $forwardto = corganization()->user(cobjectref()->attributeref('crs_management_incoming_forwardto')->value); $groups = $forwardto->groups(); $isgip = false; foreach($groups as $group) if (strncmp($group['data_one'],'09.',3) == 0) { $isgip = true; break; } if ($isgip) $to = $group->users(); else $to = $forwardto; cobjectref()->description = cobjectref()->calcIncomingDesc(); cobjectref()->status = 'crs_management_incoming_handed'; sendEmail(array( 'from'=>cuser(), 'to'=>$to, 'subj'=>cobjectref()->description.'  ', 'body'=>' !    .', 'objects'=>cobjectref(), 'roles'=>'crs_management_all', )); 

The peculiarity of the action is sending not one, but several letters in the event that the incoming document is redirected to the ISU. GIPs themselves asked to send a copy of the letter to the address of their assistant, because they may be engaged in other work or are on a business trip and the letter will “hang” indefinitely. Of course, it was clear that the GUIs are cunning, they could easily deal with the letter even remotely, but it’s pointless to enter into an argument with them. Therefore, after coordinating the decision with the top management, I implemented sending a copy of the letter to the GIP assistant (we call them “hypits”).

Add task

The service team allows you to create a task with an incoming letter at the base for opening.
 $subj = ''; if (!empty(cobjectref()->attributeref('crs_management_incoming_subj')->value)) $subj = $subj.(strlen($subj) > 0 ? ' ' : '').cobjectref()->attributeref('crs_management_incoming_subj')->value; $new_task = new Objectref(); $new_task->prepare(objectDef('tsk_management','tsk_task')); if (!empty(cobjectref()->attributeref('crs_management_incoming_contract')->value)) $new_task->attributeref('tsk_task_contract')->value = cobjectref()->attributeref('crs_management_incoming_contract')->value[0]; $new_task->attributeref('tsk_task_subj')->value = $subj; $category = classificator('task_category_answer'); if (!empty($category)) $new_task->attributeref('tsk_task_category')->value = $category->id; $new_task->attributeref('tsk_task_base_open')->value = cobjectref()->id; caction()->redirect = urlNewObjectref($new_task); 

It is useful when the incoming solution has already been considered, but someone forgot to set a task for someone. It is most convenient to create it with the help of this command.

Reviewed by

It is used in the case when the incoming document does not require the creation of tasks and should only change the status. Easier you can think of:
 cobjectref()->status = 'crs_management_incoming_ok'; 


Add contact...

In easla.com, it is possible to bind actions to object attributes. Such actions differ from the usual ones in that they are displayed not at the bottom of the object form, but next to the specified attribute. And I, as an administrator, can choose which side to place the button: top, bottom, left, right.
The point of entry for correspondence is a clerical specialist, so she regularly faces the need to register new contractors and contacts. Sometimes it is found that the missing contact is not in the list, and the form with the incoming document is almost full.
In general, I created a service function for it, which opens a new tab in the browser and allows you to add a new contact with just four lines of code:
 $new_contact = new Objectref(); $new_contact->prepare(objectDef('crm_management','crm_management_contact')); caction()->target = '_blank'; caction()->redirect = urlNewObjectref($new_contact); 

Of course, the action is tied to the attribute "Contact".

Add business partner ...

By analogy, created an action for creating a new counterparty and tied it to the "Counterparty" attribute.
 $new_contact = new Objectref(); $new_contact->prepare(objectDef('crm_management','crm_management_contragent')); caction()->target = '_blank'; caction()->redirect = urlNewObjectref($new_contact); 


Reply

Honestly, I somehow did not immediately think of this action, although the need for it was felt immediately. Indeed, in every mailer there is a button “Answer”, which creates an outgoing letter and fills in two fields: to whom and the subject. But it's good when only 2 fields need to be filled out, you can also create outgoing without a service team, and when we have 19 attributes and almost everything needs to be filled in to send an outgoing letter, the action is simply irreplaceable!
It seemed to me that I would face difficulties, but everything turned out to be simpler than I thought:
 $new_outgoing = new Objectref(); $new_outgoing->prepare(objectDef('crs_management','crs_management_outgoing')); $new_outgoing->attributeref('crs_management_outgoing_contragent')->value = cobjectref()->crs_management_incoming_contragent; $new_outgoing->attributeref('crs_management_outgoing_contact')->value = cobjectref()->crs_management_incoming_contact; $new_outgoing->attributeref('crs_management_outgoing_recipients')->value = cobjectref()->crs_management_incoming_performers; $new_outgoing->attributeref('crs_management_outgoing_responsibleuser')->value = cobjectref()->crs_management_incoming_forwardto; $new_outgoing->attributeref('crs_management_outgoing_incomingdocs')->value = cobjectref()->id; $new_outgoing->attributeref('crs_management_outgoing_contract')->value = cobjectref()->crs_management_incoming_contract; caction()->redirect = urlNewObjectref($new_outgoing); 


Notify the receipt of the original

Remember the attribute "notify the receipt of the original"? That's it for this action, he was added. When a clerk gets the original document, it causes this action and all!
 if (empty(cobjectref()->attributeref('crs_management_incoming_receive_original_date')->value)) throw new Exception('    !'); if (cobjectref()->attributeref('crs_management_incoming_receive_original_flag')->value != 1) throw new Exception('      '); $forwardto = cobjectref()->attributeref('crs_management_incoming_forwardto')->value; sendEmail(array( 'from'=>cuser(), 'to'=>corganization()->user($forwardto), 'subj'=>cobjectref()->description.'  ', 'body'=>' !     '.cobjectref()->viewLink().' ['.cobjectref()->attributeref('crs_management_incoming_contragent_regnum')->value.']' )); cobjectref()->attributeref('crs_management_incoming_receive_original_flag')->value = 2; 


Roles

By the way, it was at this stage that I remembered that I needed to decide on roles and access rights! There is such a bad trait in me, to do everything first, and then only to think who should not be given what has been done.
There was a very stormy dialogue with the CEO, and then with the chief engineer, from which I took the need to create the following roles:

This is how the access rights to the incoming document were distributed.

But that's not all.Our incoming documents need to be very selectively assigned access rights in accordance with the value of the "Content Type" attribute. In general, in the “After saving an object” script, added the assignment of rights to the newly saved object:
 if (!empty(cobjectref()->attributeref('crs_management_incoming_content')->value)) { $c = classificator(cobjectref()->attributeref('crs_management_incoming_content')->value); // if ($c->code == 'crs_content_claim') { cobjectref()->addRolesPermissions(array( 'crs_management_tops', 'crs_management_buh', 'crs_management_gip', 'crs_management_dp', 'crs_management_pmo', )); } // //, ,  elseif (in_array($c->code, array('crs_content_ruling','crs_content_review','crs_content_decision'))) { cobjectref()->addRolesPermissions(array( 'crs_management_tops', 'crs_management_buh', 'crs_management_dp', 'crs_management_gip', )); } } 

As can be seen from the code, the number of roles is even redundant, but I did not reduce their number just in case. Suddenly, they will ask for unique rights for some particular type of content tomorrow.

Bye all


About setting up the object "Outgoing object" in the next part . Thanks to everyone who read to this place. I hope everyone found something useful for themselves.
PS The “Incoming Document” object described above is fully implemented in the process published for all Correspondence . Borrowing the process you can not lose time writing the same amount, albeit simple, code.

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


All Articles