📜 ⬆️ ⬇️

The experience of automating difficult correspondence (Part 2. Outgoing)

I want to continue the description of my experience in improving the process of managing official correspondences in the project organization, begun in the previous article . Let me remind you that instead of elma, the easla.com system was chosen and it already described the behavior of the incoming document.

The next step to the goal is to describe the behavior of the outgoing document. Those were flowers ... under the cut even more text and code.

The requirements for the outgoing document were several times more serious than for the incoming one. If you collect only the main ones in a separate summary list, you get:

As you can see, the requirements are not simple. That is why I was so attentive to the choice of the new system and stopped at easla.com . But let's tell in order.
I began by creating the “Outgoing Document” object in the “Correspondence” process and moving on to filling it with attributes. They are even more than in the incoming!

Attributes


Counterparty

Link to the “Counterparty” object from the “Customers” process. Initialization of valid attribute values ​​is performed in the “After object initialization” script:
cobjectref()->attributeref('crs_management_outgoing_contragent')->values = prepareOutgoingContragents(); 

The auxiliary function prepareOutgoingContragents is declared in the script “Before the object is initialized”:
 function prepareOutgoingContragents() { $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; } 

Depending on the chosen contractor, the list of available contacts and the recommended rule for sending a letter depend. And if the behavior with contacts is clear to everyone, then with the rule of sending - not everyone. The fact is that our contractors, especially customers, are very capricious in terms of receiving email. mail. I find it difficult to explain the reasons for this behavior, but, for example, some customers accept email. letters with a maximum capacity of 3MB and at the same time require that all documentation, which may be 100MB in size, be sent to them exclusively by email. mail. Others allow the transfer of large investments in email. Letters, say, about 10-15 MB, but require that all applications come only in zip format and in no case a multi-volume archive. Still others insist that the cover letter be sent an email. mail to their corporate address, and all applications were transferred in a different way to other recipients. The fourth asked to upload attachments to letters on the private "ball" so that they can download everything from there. A couple of weeks ago, one customer asked to transfer all applications to secure FTP when sending a letter, and another demanded that all applications and a cover letter be uploaded to them on the portal raised on FrontPage and placed in certain folders.
It was obvious that during intensive correspondence, manually meeting the requirements of all customers for sending letters would be difficult. Perhaps, but very difficult. It will be necessary for both the clerk and the GUIs to remember exactly how to send a letter to a specific customer. Bullying, not work!
I had to break my head a little, as if to save all these requirements in easla.com , in order to facilitate the work of all participants in the process. As a result, I modified the Counterparty object in the Customers process by adding two attributes to it: one to store the recommended sending rule (more about it below), the second for the list of additional contacts (it will also be more detailed about it).
In short, the “At Change” attribute script allows you to redefine the attribute values ​​in accordance with the selected counterparty:
 if (!empty(cattributeref()->value)) { $contacts = cobjectref()->prepareOutgoingContacts(cattributeref()->value); cobjectref()->attributeref('crs_management_outgoing_contact')->values = $contacts; $contracts = cobjectref()->prepareContracts(cattributeref()->value); if (empty($contracts)) $contracts = cobjectref()->prepareContracts(); cobjectref()->attributeref('crs_management_outgoing_contract')->values = $contracts; $default_rule = cobjectref()->calcContragentOutgoingRule(cattributeref()->value); if (!empty($default_rule)) cobjectref()->attributeref('crs_management_outgoing_rule')->value = $default_rule; $default_notifiers = cobjectref()->calcContragentOutgoingNotifiers(cattributeref()->value); if (!empty($default_notifiers)) cobjectref()->attributeref('crs_management_outgoing_notifiers')->value = $default_notifiers; //if (cobjectref()->attributeref('crs_management_outgoing_contragent')->value == 45410) } cobjectref()->attributeref('crs_management_outgoing_cntnum')->value = cobjectref()->updateDocumentCntNum(); cobjectref()->attributeref('crs_management_outgoing_regnum')->value = cobjectref()->calcOutgoingCode(); 

By the way, the last two lines of the script have also been added recently. Due to the fact that one customer demanded that we use to identify outgoing documents, their designations that are different from ours. Here they are - customers!

Contact

Reference to the object "Contact" in the process "Customers". Like the list of contractors, the contact list is initialized in the “After object initialization” script:
 $contacts = prepareOutgoingContacts(); cobjectref()->attributeref('crs_management_outgoing_contact')->values = $contacts; 

At the same time, the auxiliary function prepareOutgoingContacts is also declared in the script “Before an object is initialized”:
 function prepareOutgoingContacts($contragent = null) { if (empty($contragent)) $src_contacts = selectAll( 'crm_management', 'crm_management_contact', array('crm_management_contact_contragent.description'), null ); 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, like its “sister” in the “Incoming Document” object, adds to all complete namesake the name of the organization in which they work so that they can be distinguished.
For the sake of additional convenience, the “On Change” contact script and the unspecified counterparty substitutes the required counterparty and updates the values ​​of the attributes dependent on it:
 if (empty(cobjectref()->attributeref('crs_management_outgoing_contact')->value)) return; if (empty(cobjectref()->attributeref('crs_management_outgoing_contragent')->value)) { $contact = select(cobjectref()->attributeref('crs_management_outgoing_contact')->value); if (empty($contact)) return; $contragent_id = $contact->attributeref('crm_management_contact_contragent')->value; cobjectref()->attributeref('crs_management_outgoing_contragent')->value = $contragent_id; $contracts = cobjectref()->prepareContracts($contragent_id); if (empty($contracts)) $contracts = cobjectref()->prepareContracts(); cobjectref()->attributeref('crs_management_outgoing_contract')->values = $contracts; $default_rule = cobjectref()->calcContragentOutgoingRule($contragent_id); if (!empty($default_rule)) cobjectref()->attributeref('crs_management_outgoing_rule')->value = $default_rule; $default_notifiers = cobjectref()->calcContragentOutgoingNotifiers($contragent_id); if (!empty($default_notifiers)) cobjectref()->attributeref('crs_management_outgoing_notifiers')->value = $default_notifiers; } cobjectref()->attributeref('crs_management_outgoing_cntnum')->value = cobjectref()->updateDocumentCntNum(); cobjectref()->attributeref('crs_management_outgoing_regnum')->value = cobjectref()->calcOutgoingCode(); 

The result looks like this:


Add. contacts

Reference to the object "Contact" in the process "Customers". It is a list of additional contacts that will be put in a copy when sending a letter to the main recipients. This attribute records contacts indicated in the “Counterparty” object, to which they need to send copies of the letter, if it is sent to a specific counterparty.
The implementation was very convenient, because Previously, a clerk and screening officer stuck their monitors with sticky papers, on which were written the names of the customer and the list of persons to whom to send copies of letters. Instead of paperwork , easla.com is now used , and no one forgets to indicate contacts in a copy.
')
Add. notify by email mail

Reference to the object "Contact" in the process "Customers". Another list of additional. contacts, which must be separately notified by a copy of the letter. Often it may not even be employees of the customer, but, say, employees of the parent organization or supervisory authority.

Send rule

Classifier that allows you to determine how to send letters to the addressee. Today, we use 16 rules of dispatch. Some differ only in numbers, for example:

The list of valid values ​​is formed in the “At Initialization” attribute script:
 $src_classificators = classificatorChilds('crs_outgoing_rule'); $end_classificators = array(); $default = 'crs_outgoing_rule_asis_max5'; $default_index = null; foreach($src_classificators as $c) { $end_classificators += array($c['id']=>$c['name']); if ($c['code'] == $default) $default_index = $c['id']; } if (count($end_classificators) > 0) { cattributeref()->values=$end_classificators; cattributeref()->value = is_null($default_index) ? key($end_classificators) : $default_index; } 

A useful classificatorChilds function returns the direct descendants of the specified classifier. It remains only to iterate over the values, form an array and assign to the attribute.
The sending rule is used in the action “Send by email. mail, which will be described below.

Registration date

Obviously from the name that stores the date of registration of the outgoing document. The initial value corresponds to the current date and is initialized in the “At Initialization” attribute script:
 cobjectref()->attributeref('crs_management_outgoing_regdate')->value = currentDateTime(); 


departure date

It is also clear from the name that the attribute stores the date of dispatch. It is read only, i.e. The user himself can not fill it. In the read-only mode, the attribute is translated in the “At Initialization” script:
 cattributeref()->readonly = true; 


Performers

Users (employees) organizations involved in the development of the letter. The attribute is multiple, since several employees can participate in the development of a letter. During the registration (creation) of a letter, the current user is identified and, if he does not belong to a narrow circle of persons delineated by the groups specified in the code, then it is written into the attribute value. Each subsequent employee must add himself to the list of performers if he wants to receive information about sending a letter. By the way, the performers are also indicated at the end of the outgoing letter form indicating their telephone numbers for feedback.
Initialization of valid attribute values ​​occurs in the "At Initialization" 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_outgoing_performers')->values = $end_users; if (!empty(cuser())) { $rsp_users = corganization()->allUsersByGroups(array( 'group_general_manager', 'group_general_engineer', 'group_general_manager_operations', 'group_general_manager_economics', 'group_gip', 'group_hr', 'group_dp' ), null); $f = true; foreach($rsp_users as $u) if ($u['id'] == cuser()->id) { $f = false; break; } if ($f) { cobjectref()->attributeref('crs_management_outgoing_performers')->value = cuser()->id; cobjectref()->updateResponsibleGroup(); } } 

The updateResponsibleGroup helper function is designed to update the value of the attribute “Reply. subdivision "and declared in the script" Before the object is initialized ":
 function updateResponsibleGroup() { if (empty(cobjectref()->attributeref('crs_management_outgoing_performers')->value)) return; if (empty(cobjectref()->attributeref('crs_management_outgoing_responsiblegroup')->value)) { $performers = cobjectref()->attributeref('crs_management_outgoing_performers')->value; $user = corganization()->user($performers[0]); if (empty($user)) return; $groups = $user->groups(); foreach($groups as $group) if (!empty($group['data_one'])) { cobjectref()->attributeref('crs_management_outgoing_responsiblegroup')->value = $group['id']; break; } } } 

If the attribute value changes, the “On Change” script is triggered, which updates the responsible department in the form of an outgoing document:
 if (!empty(cobjectref()->attributeref('crs_management_outgoing_performers')->value) && empty(cobjectref()->attributeref('crs_management_outgoing_responsiblegroup')->value)) cobjectref()->updateResponsibleGroup(); 


Ed. employee

Users (employees) of the organization that will sign the outgoing letter. In our case, official letters are signed only by senior managers, the head of the personnel department and the GUIs. The list of eligible employees is formed in the "At Initialization" script:
 $src_users = corganization()->allUsersByGroups(array( 'group_general_manager', 'group_general_engineer', 'group_general_manager_operations', 'group_general_manager_economics', 'group_gip_only', 'group_hr' ), 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_outgoing_responsibleuser')->values = $end_users; 

The allUsersByGroups function is simply a “magic wand” when you need to get a list of users in certain groups or vice versa.
When an attribute is changed, a value is checked in the attribute “Reply. subdivision". In the event that it is absent, i.e. the letter does not specify the performer, it records the unit to which the responsible person belongs. employee:
 if (!empty(cattributeref()->value) && empty(cobjectref()->attributeref('crs_management_outgoing_responsiblegroup')->value)) { $user = corganization()->user(cattributeref()->value); $groups = $user->groups(); foreach ($groups as $group) if (is_numeric($group->data_one)) { cobjectref()->attributeref('crs_management_outgoing_responsiblegroup')->value = $group->id; break; } } cobjectref()->attributeref('crs_management_outgoing_regnum')->value = cobjectref()->calcOutgoingCode(); 

Very pleased with the opportunity to create such dynamic forms in easla.com. By changing the values ​​of some attributes, I can change the values ​​of others in any sequence! Try to do the same, for example, in Sharepoint.

Ed. subdivision

A group of users, in our case - a division of the organization that is responsible for developing the outgoing document.
All units in our organization have their own numerical (fractional) identifiers assigned by the personnel department. Probably, they are used somewhere else, but in my case, they are used to identify the outgoing letter. Therefore, when I created groups for users in easla.com, I added these identifiers to the so-called “first data”.

When forming the list of possible otv. subdivisions of the “During Initialization” script checks for the presence of “first data” and includes groups in the list only if they are available:
 $src_groups = corganization()->groups(); $end_groups = array(); foreach($src_groups as $g) if (!empty($g['data_one'])) $end_groups += array($g['id']=>$g['name']); asort($end_groups); cobjectref()->attributeref('crs_management_outgoing_responsiblegroup')->values = $end_groups; 

When changing the answer units change the designation of the registration number of the outgoing document:
 cobjectref()->attributeref('crs_management_outgoing_regnum')->value = cobjectref()->calcOutgoingCode(); 

The helper function calcOutgoingCode is declared in the script “Before the object is initialized”:
 function calcOutgoingCode() { if (empty(cobjectref()->attributeref('crs_management_outgoing_contragent')->value)) return; if (empty(cobjectref()->attributeref('crs_management_outgoing_cntnum')->value)) return; if (!empty(cobjectref()->attributeref('crs_management_outgoing_content')->value) && cobjectref()->attributeref('crs_management_outgoing_contragent')->value == 45410 && cobjectref()->attributeref('crs_management_outgoing_content')->value == 1192) { //This is ZapSib-2 Project if (empty(cobjectref()->attributeref('crs_management_outgoing_content')->value)) return; $content = classificator(cobjectref()->attributeref('crs_management_outgoing_content')->value); if (empty($content)) return; $content_code = $content['data_one']; if (empty($content_code)) return; //TRANSMITTAL return sprintf('TNGP-NPG-ZS2-0311-%s-%05d', $content_code, cobjectref()->attributeref('crs_management_outgoing_cntnum')->value ); } if (empty(cobjectref()->attributeref('crs_management_outgoing_responsibleuser')->value)) return; if (empty(cobjectref()->attributeref('crs_management_outgoing_responsiblegroup')->value)) return; $group_code = str_replace('.','-',corganization()->group(cobjectref()->attributeref('crs_management_outgoing_responsiblegroup')->value)->data_one); $user_code = mb_substr(corganization()->allUser(cobjectref()->attributeref('crs_management_outgoing_responsibleuser')->value)->lastname, 0, 2); return sprintf( '%s/%s-%s', $group_code, $user_code, cobjectref()->attributeref('crs_management_outgoing_cntnum')->value ); } 

Until recently, the function was much simpler, but due to recent requirements, it was necessary to make a change to it, which takes into account the identifier of the customer and the type of letter. In accordance with the completed attributes, the function generates the registration number of the document.

Serial number

The ordinal number of the outgoing document begins its numbering every year anew. Therefore, it was impossible to use the numerator built into easla.com . I had to write my own.
In addition, one of the customers demanded the use of pass-through numbering for a certain type of his letters, so the numbering is double. Normal for letters with our registration number and unusual for letters with a registration number requested by the customer.
When creating an outgoing document, the initial sequence number is calculated:
 cattributeref()->value = cobjectref()->updateDocumentCntNum(); 

The auxiliary function updateDocumentCntNum is also declared in the "Before an object is initialized" script:
 function updateDocumentCntNum() { if (cobjectref()->isNewRecord || empty(cobjectref()->attributeref('crs_management_outgoing_cntnum')->value)) { if (!empty(cobjectref()->attributeref('crs_management_outgoing_contragent')->value) && !empty(cobjectref()->attributeref('crs_management_outgoing_content')->value) && cobjectref()->attributeref('crs_management_outgoing_contragent')->value == 45410 && cobjectref()->attributeref('crs_management_outgoing_content')->value == 1192) { $num = selectAggregateAll( 'max', 'crs_management', 'crs_management_outgoing', 'crs_management_outgoing_cntnum', array( 'crs_management_outgoing_contragent'=>array('id',cobjectref()->attributeref('crs_management_outgoing_contragent')->value), 'crs_management_outgoing_content'=>array('id',cobjectref()->attributeref('crs_management_outgoing_content')->value) ) ); return $num + 1; } $year = date_format(currentDateTime(), 'Y'); $condition = array( 'crs_management_outgoing_regdate'=>array('between', $year.'-01-01', $year.'-12-31'), ); switch ($year) { case '2016': $condition['crs_management_outgoing_cntnum'] = '<4489'; $tmp = selectAggregateAll( 'max', 'crs_management', 'crs_management_outgoing', 'crs_management_outgoing_cntnum', $condition ); if ($tmp == 4488) { unset($condition['crs_management_outgoing_cntnum']); } else { return $tmp + 1; } break; } $num = selectAggregateAll( 'max', 'crs_management', 'crs_management_outgoing', 'crs_management_outgoing_cntnum', $condition ); return $num + 1; } else { return cobjectref()->attributeref('crs_management_outgoing_cntnum')->value; } } 

If you look closely at the code, you will see a strange case condition '2016'. This is the result of our internal problems. We also have the registration of documents "retroactively", so in easla.com I did not make a "cut-off" to try to cheat. However, everything is on the responsibility of the user. So we created letters with last year’s numbers at the beginning of this year. And even managed to send them! I had to set a condition and cut off such letters in order to correctly number the letters this year.
I will separately pay attention to the selectAggregateAll function. It returns the aggregated value, in my case, the maximum numeric value of the attribute 'crs_management_outgoing_cntnum' of the object 'crs_management_outgoing' in the process 'crs_management'. It uses the search condition $ condition, which is formed slightly higher.

In the same elma I had to abandon this method in favor of a workaround. Here's how it was:
Correct but brake code in elma
 var manager = EntityManager<IOutgoingDoc>.Instance; DateTime startdate = new DateTime(entity.RegDate.Value.Year, 1, 1); DateTime enddate = new DateTime(entity.RegDate.Value.Year, 12, 31); var allInYear = from d in manager.FindAll() where d.RegDate.Value >= startdate && d.RegDate.Value <= enddate orderby d.CntNum select d; if (allInYear.Count() == 0) entity.CntNum = 1; else entity.CntNum = allInYear.Last().CntNum + 1; 


Everything worked for the time being, but then it began to slow down so much that I had to look for bottlenecks. It turned out that one such place is the calculation of the sequence number. I had to create additional. tables and replace the code to increase the speed of the object and the system as a whole:
Bypass maneuver in elma
 string ConString = "Data Source=ss2;Initial Catalog=ELMA;User ID=sa;Password=password;"; using (SqlConnection connection = new SqlConnection(ConString)) { using (SqlCommand command = connection.CreateCommand()) { var year = entity.RegDate.Value.Year; command.CommandText = "SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SELECT TOP 1 cntNum FROM [ELMA].[dbo].[OutgoingDoc] WHERE (RegDate >= CAST('" + year.ToString() + "-01-01 00:00:00' AS datetime)) AND (RegDate < CAST('" + year.ToString() + "-12-31 23:59:59' AS datetime)) ORDER BY cntNum DESC"; connection.Open(); SqlDataReader reader = command.ExecuteReader(); var cntNum = 0; if (reader.Read()) cntNum = int.Parse(reader[0].ToString()); connection.Close(); command.CommandText = "SELECT TOP 1 reservedNum FROM [TNGP].[dbo].[ElmaReservedNums] WHERE docType = '" entity.TypeUid + "' ORDER BY reservedNum DESC"; connection.Open(); reader = command.ExecuteReader(); var reservedNum = 0; if (reader.Read()) reservedNum = int.Parse(reader[0].ToString()); connection.Close(); if (cntNum < reservedNum) cntNum = reservedNum; if (cntNum == 0) cntNum = 1; else cntNum++; command.CommandText = "INSERT INTO [TNGP].[dbo].[ElmaReservedNums](reservedNum, docType) VALUES (" + cntNum.ToString() + ",'" + entity.TypeUid + "')"; connection.Open(); command.ExecuteNonQuery(); connection.Close(); entity.CntNum = cntNum; } } 


Is this a thing ?!

Registration number

Required text attribute is read only. Stores the registration number of the outgoing document. Calculated during initialization:
 cattributeref()->readonly = true; cattributeref()->value = cobjectref()->calcOutgoingCode(); 

Updated by changing other attributes whose values ​​affect the registration number of the document.


Theme

Normal multi-line required text attribute.

Type of Content

Classifier, allowing to determine the type of content of the letter. Exactly the same as that of the incoming document. The list of valid values ​​is formed during initialization:
 $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) { cattributeref()->values=$end_classificators; cattributeref()->value = isset($default) ? $default : $c['id']; } 

, , .
 if (cobjectref()->attributeref('crs_management_outgoing_contragent')->value == 45410) cobjectref()->attributeref('crs_management_outgoing_cntnum')->value = cobjectref()->updateDocumentCntNum(); cobjectref()->attributeref('crs_management_outgoing_regnum')->value = cobjectref()->calcOutgoingCode(); 



« » . , " " « ». --- ..
, , , . «».
« »:
 cattributeref()->values = cobjectref()->prepareIncomings(); 

prepareIncomings « »:
 function prepareIncomings() { $src_documents = selectAll( 'crs_management', 'crs_management_incoming', array('crs_management_incoming_contragent_regnum') ); $end_documents = array(); foreach ($src_documents as $d) $end_documents += array($d['id'] => $d['crs_management_incoming_contragent_regnum'].' ['.$d['description'].']'); asort($end_documents); return $end_documents; } 


Contract

«» «». , " " « ».
« », :
 cobjectref()->attributeref('crs_management_outgoing_contract')->values = prepareContracts(); 

, , .


, .. , .. . , , , - .


. …

..

. , . .

Document

. , , , .
« »:
 cattributeref()->revMode = true; cattributeref()->canScan = true; cattributeref()->fileLinks = 3;//1;//2; 

. revMode , / ().


canScan , .


fileLinks=3 , , ( Easla Agent ). , , . 0, .. , . 1 – , . , .. , , 2 – , . . 3 – , (. ).

, easla.com . . . .


, , , Microsoft Word, . , , . , , . Microsoft Word .


, .. , , :
 cobjectref()->updateDocumentFileName(); 

. , , , , , , .
, « »:
 function calcOutgoingDesc() { $code = calcOutgoingCode(); if (empty($code)) return; if (empty(cobjectref()->attributeref('crs_management_outgoing_sentdate')->value)) { $d = date_create(cobjectref()->attributeref('crs_management_outgoing_regdate')->value); } else { $d = date_create(cobjectref()->attributeref('crs_management_outgoing_sentdate')->value); } return sprintf( '%s  %s', $code, localeFormatDate($d) ); } function updateDocumentFileName() { $desc = calcOutgoingDesc(); if (empty($desc)) return; $files = cobjectref()->attributeref('crs_management_outgoing_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(); } } 

calcOutgoingDesc .

Applications

. , . « » :
 if (cattributeref()->filesCount > 10) cattributeref()->fileInfo = array('revcode','modifytime','count','total','header','filter'); else cattributeref()->fileInfo = array('revcode','modifytime','count','total'); 

, . mail.
, TDMS , - . . , , . , TDMS easla.com , , .
:

, 2 5 . 10 , ! Nightmare!
, TDMS , , !
, , easla.com TDMS . , TDMS , . , ( easla.com SOAP ), !

On this topic, you can write a separate article, if anyone will be interested. The solution is highly specialized, but the experience is very useful!

Link to apps elsewhere

A text attribute for storing a hyperlink pointing to storing applications somewhere else. Say, on a public or corporate file sharing.

Text of the letter

Recently added attribute. Designed to write the text of the letter, which does not imply the presence of the accompanying document. Currently used for only one type of content - Transmittal.

An object


Just like the list of incoming documents, the list of outgoing documents is colored according to their status. It was enough to add code to the “After object initialization” script:
 switch (cobjectref()->status->code) { case 'crs_management_outgoing_created': cobjectref()->status->state = 1; break; case 'crs_management_outgoing_fax': case 'crs_management_outgoing_courier': case 'crs_management_outgoing_narochnym': case 'crs_management_outgoing_post': cobjectref()->status->state = 2; break; case 'crs_management_outgoing_email': cobjectref()->status->state = 4; break; } 

Agree, color adds visibility.


The final validation of an object's attributes is performed in the “Before an object is saved” script:
 cobjectref()->attributeref('crs_management_outgoing_regnum')->value = calcOutgoingCode(); cobjectref()->description = calcOutgoingDesc(); updateDocumentFileName(); if (!empty(cobjectref()->attributeref('crs_management_outgoing_cntnum')->value)) { if (cobjectref()->attributeref('crs_management_outgoing_contragent')->value == 45410 && cobjectref()->attributeref('crs_management_outgoing_content')->value == 1192) { $conditions = array( 'crs_management_outgoing_contragent'=>array('id',cobjectref()->attributeref('crs_management_outgoing_contragent')->value), 'crs_management_outgoing_content'=>array('id',cobjectref()->attributeref('crs_management_outgoing_content')->value), 'crs_management_outgoing_cntnum'=>cobjectref()->attributeref('crs_management_outgoing_cntnum')->value ); if (!cobjectref()->isNewRecord) $conditions['id'] = '<>'.cobjectref()->id; $exist = selectCountAll('crs_management','crs_management_outgoing', $conditions); if ($exist) { $nownum = cobjectref()->attributeref('crs_management_outgoing_cntnum')->value; $freenum = updateDocumentCntNum(); cobjectref()->attributeref('crs_management_outgoing_cntnum')->value = $freenum; cobjectref()->attributeref('crs_management_outgoing_regnum')->value = calcOutgoingCode(); updateDocumentFileName(); throw new Exception('  , ..     .  '.$nownum.'!    '.$freenum.',    .'); } } else { $year = date_format(date_create(cobjectref()->attributeref('crs_management_outgoing_regdate')->value), 'Y'); $conditions = array( 'crs_management_outgoing_cntnum'=>cobjectref()->attributeref('crs_management_outgoing_cntnum')->value, 'crs_management_outgoing_regdate'=>array('between', $year.'-01-01', $year.'-12-31') ); if (!cobjectref()->isNewRecord) $conditions['id'] = '<>'.cobjectref()->id; $exist = selectCountAll('crs_management','crs_management_outgoing', $conditions); if ($exist) { $nownum = cobjectref()->attributeref('crs_management_outgoing_cntnum')->value; $freenum = updateDocumentCntNum(); cobjectref()->attributeref('crs_management_outgoing_cntnum')->value = $freenum; cobjectref()->attributeref('crs_management_outgoing_regnum')->value = calcOutgoingCode(); updateDocumentFileName(); throw new Exception('  , ..     .  '.$nownum.'   '.$year.' !    '.$freenum.',    .'); } } } if (cobjectref()->status->code == 'crs_management_outgoing_create') { cobjectref()->status = 'crs_management_outgoing_created'; cobjectref()->flags = 1; } else { cobjectref()->flags = 0; } 

Here the status of the object changes.
In the “After saving an object” script, rights are assigned to the saved object:
 if (empty(cobjectref()->crs_management_outgoing_responsibleuser)) return; if (empty(cobjectref()->crs_management_outgoing_responsiblegroup)) return; $ruser = corganization()->user(cobjectref()->crs_management_outgoing_responsibleuser); if (empty($ruser)) return; $rgroup = corganization()->group(cobjectref()->crs_management_outgoing_responsiblegroup); if (empty($rgroup)) return; $rugroups = $ruser->groups(); foreach ($rugroups as &$rug) $rug = $rug['code']; if ($rgroup->code == 'group_general_manager' && in_array($rgroup->code, $rugroups)) { $c = classificator(cobjectref()->attributeref('crs_management_outgoing_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', )); } else { cobjectref()->addRolesPermissions(array( 'crs_management_tops', 'crs_management_dp', )); } } elseif ($rgroup->code == 'group_pdg') { cobjectref()->addRolesPermissions(array( 'crs_management_tops', // 'crs_management_buh', 'crs_management_gip', 'crs_management_dp', 'crs_management_pmo', )); } else cobjectref()->resetRolesPermissions(); 


Forms


easla.com . . , . «» «», .
: . , : , .. . , . , .


Statuses


, , , . :

:

, , , . , , .. . , .

Actions


, – , . , «» «» .

[…]

, , :

, :
 if (empty(cobjectref()->attributeref('crs_management_outgoing_sentdate')->value)) { cobjectref()->attributeref('crs_management_outgoing_sentdate')->value = currentDateTime(); } cobjectref()->status = 'crs_management_outgoing_courier'; $msg = cobjectref()->commentTasks(); if (!empty($msg)) echo implode('',$msg); 

'crs_management_outgoing_courier' .

. contacts

, «. » . , , . . . :
 $contacts = cobjectref()->prepareOutgoingContacts(); asort($contacts); cobjectref()->attributeref('crs_management_outgoing_recipients')->values = $contacts; 


Add.

. , . :
 if (!empty(cobjectref()->attributeref('crs_management_outgoing_contragent')->value)) { $contacts = cobjectref()->prepareOutgoingContacts(cobjectref()->attributeref('crs_management_outgoing_contragent')->value); cobjectref()->attributeref('crs_management_outgoing_recipients')->values = $contacts; } 

, « » prepareOutgoingContacts. – , – . , «» , «».


, , , . , , ! :
 if (empty(cobjectref()->crs_management_outgoing_performers)) throw new Exception('   .  !'); if (empty(cobjectref()->crs_management_outgoing_contragentdate)) throw new Exception('       !'); if (cobjectref()->hasAttributeref('crs_management_outgoing_contragentperson') && empty(cobjectref()->crs_management_outgoing_contragentperson)) throw new Exception('     !'); cobjectref()->description = cobjectref()->calcOutgoingDesc(); $to = corganization()->users(cobjectref()->crs_management_outgoing_performers); $body = array( ' !', ' ', ': '.cobjectref()->viewLink(), ': '.cobjectref()->crs_management_outgoing_subj, '  .', ': '.(cobjectref()->hasAttributeref('crs_management_outgoing_contragentperson') ? cobjectref()->crs_management_outgoing_contragentperson : ''), '   : '.cobjectref()->crs_management_outgoing_contragentdate, ' ', ' ,', cuser()->description ); $options = array( 'from'=>cuser(), 'to'=>$to, 'subj'=>'   '.cobjectref()->description, 'body'=>implode('',$body), ); $options['bcc'] = cuser(); sendEmail($options); $message = array(); foreach ($to as $u) $message[] = $u->viewLink(); echo '   :'.implode('', $message); //   caction()->result = ' : '.implode(',', $message); 

, easla.com . sendMail ! , . It's simple!


. , . , , « ». easla.com , «» , . , . . . .
. , , . :
 $odt = date_create(); $cdt = date_add(date_create(), new DateInterval('P1D')); $share = shareFiles( cobjectref(), array('crs_management_outgoing_document','crs_management_outgoing_attachments'), $odt, $cdt, cuser(), 'CP866' ); if (empty($share)) throw new Exception("      !"); echo '      : '.$share->link(); 

, , .



- , . , « . » . …
, , . :
 cobjectref()->status = 'crs_management_outgoing_created'; 



, , . «» . :
 $new_notification = new Objectref(); $new_notification->prepare(objectDef('tsk_management','tsk_notification')); $new_notification->attributeref('tsk_notification_subj')->value = '   '.cobjectref()->description; $new_notification->attributeref('tsk_notification_base')->value = cobjectref()->id; caction()->redirect = urlNewObjectref($new_notification); 



. , , «», :
 $subj = ''; if (!empty(cobjectref()->attributeref('crs_management_outgoing_subj')->value)) $subj = $subj.(strlen($subj) > 0 ? ' ' : '').cobjectref()->attributeref('crs_management_outgoing_subj')->value; $new_task = new Objectref(); $new_task->prepare(objectDef('tsk_management','tsk_task')); $new_task->attributeref('tsk_task_subj')->value = $subj; $new_task->attributeref('tsk_task_base_open')->value = cobjectref()->id; caction()->redirect = urlNewObjectref($new_task); 


, , . ! , ! !

.

! . ! , , .
, , 3 , 200 . , .. « » . , , , - / / , , , :
— , ! You are welcome!
— ? !
— ?
— , .
— ?
— !
On that and decided. 9 . . . « . ». 5! , 3 !
, , PHP :
- .
 if (empty(cobjectref()->attributeref('crs_management_outgoing_regnum')->value)) throw new Exception('      !'); if (empty(cobjectref()->attributeref('crs_management_outgoing_subj')->value)) throw new Exception('     !'); function contactEmail($contact) { if (empty($contact)) return false; $emails = $contact->attributeref('crm_management_contact_email')->value; if (empty($emails)) return false; return $emails[0]; } function contragentEmail($contagent) { if (empty($contagent)) return false; $emails = $contagent->attributeref('crm_management_contragent_email')->value; if (empty($emails)) return false; return $emails[0]; } function getEmail($contact) { $e = contactEmail($contact); if ($e === false) { if (empty($contact->attributeref('crm_management_contact_contragent')->value)) throw new Exception("     ".$contact->viewLink()); $contragent = select($contact->attributeref('crm_management_contact_contragent')->value); $e = contragentEmail($contragent); } return $e === false ? false : array($e => $contact->description); } function getMainEmail($contact) { $e = contactEmail($contact); if ($e === false) $email_1 = false; else $email_1 = array($e => $contact->description); $contragent = select($contact->attributeref('crm_management_contact_contragent')->value); $e = contragentEmail($contragent); if ($e === false) $email_2 = false; else $email_2 = array($e => $contragent->description); $emails = array(); if ($email_1) $emails += $email_1; if ($email_2) $emails += $email_2; unset($email_1, $email_2); return $emails === array() ? false : $emails; } function sendEmailDocumentOnly(&$msg, $objref, $to, $cc, $user, $checkSize = 5120000, $maxSize = '4500k', $extra_msg = '') { $dfs = $objref->attributeref('crs_management_outgoing_document')->filesSize; $body = array( ' !', ' ', '     .', easla.com '    '.$objref->description.'.', $extra_msg, ',       @sngp.ru', ' ', ' ,', $user->description ); if ($dfs < $checkSize) $files = array('compress'=>'zip', 'codepage'=>'CP866'); else $files = array('compress'=>array('maxSize'=>$maxSize)); $files['attributeCodes'] = 'crs_management_outgoing_document'; $options = array( 'from'=>$user, 'to'=>$to, 'subj'=>$objref->description.' '.$objref->attributeref('crs_management_outgoing_subj')->value, 'body'=>implode('',$body), 'objects'=>$objref, 'files'=>$files ); if (!empty($cc)) $options['cc'] = $cc; $options['bcc'] = $user; sendEmail($options); $rcvs = array(); foreach ($to as $e=>$d) $rcvs[] = corganization()->object($d)->viewLink().' ('.$e.')'; if (isset($cc)) foreach ($cc as $e=>$d) $rcvs[] = corganization()->object($d)->viewLink().' ('.$e.')'; $msg = '   : '.implode(',', $rcvs).'   : '.$dfs; } function sendEmailDocumentAndAttachments(&$msg, $objref, $to, $cc, $user, $checkSize = 5120000, $maxSize = '4500k', $extra_msg = '') { $dfs = $objref->attributeref('crs_management_outgoing_document')->filesSize; $afs = $objref->attributeref('crs_management_outgoing_attachments')->filesSize; $body = array( ' !', ' ', '     .', easla.com '    '.$objref->description.($afs > 0 ? '  ' : '').'.', $extra_msg, ',       @sngp.ru', ' ', ' ,', $user->description ); if ($dfs +$afs < $checkSize) $files = array('compress'=>'zip', 'codepage'=>'CP866'); else $files = array('compress'=>array('maxSize'=>$maxSize)); $options = array( 'from'=>$user, 'to'=>$to, 'subj'=>$objref->description.' '.$objref->attributeref('crs_management_outgoing_subj')->value, 'body'=>implode('',$body), 'objects'=>$objref, 'files'=>$files ); if (!empty($cc)) $options['cc'] = $cc; $options['bcc'] = $user; sendEmail($options); $rcvs = array(); foreach ($to as $e=>$d) $rcvs[] = corganization()->object($d)->viewLink().' ('.$e.')'; if (isset($cc)) foreach ($cc as $e=>$d) $rcvs[] = corganization()->object($d)->viewLink().' ('.$e.')'; $msg = '     : '.implode(',', $rcvs).'   : '.($dfs+$afs); } function sendShareDocumentAndAttachments(&$msg, $objref, $to, $cc, $user, $extra_msg = '') { $odt = date_create(); $cdt = date_add(date_create(), new DateInterval('P14D')); $share = shareFiles( cobjectref(), array('crs_management_outgoing_document','crs_management_outgoing_attachments'), $odt, $cdt, $user, 'CP866' ); $body = array( ' !', ' ', '     .', easla.com '          '.$share->link().'.', $extra_msg, ',       @sngp.ru', ' ', ' ,', $user->description ); $options = array( 'from'=>$user, 'to'=>$to, 'subj'=>$objref->description.' '.$objref->attributeref('crs_management_outgoing_subj')->value, 'body'=>implode('',$body), ); if (!empty($cc)) $options['cc'] = $cc; $options['bcc'] = $user; sendEmail($options); $rcvs = array(); foreach ($to as $e=>$d) $rcvs[] = corganization()->object($d)->viewLink().' ('.$e.')'; if (isset($cc)) foreach ($cc as $e=>$d) $rcvs[] = corganization()->object($d)->viewLink().' ('.$e.')'; $msg = '   '.$share->link().'       : '.implode(',', $rcvs); } function shareAttachments(&$msg, $objref, $user) { $odt = date_create(); $cdt = date_add(date_create(), new DateInterval('P14D')); $share = shareFiles( cobjectref(), array('crs_management_outgoing_attachments'), $odt, $cdt, $user, 'CP866' ); $msg = '        '.$share->link(); } function sendAsIsDocumentAndAttachments(&$msg, $objref, $to, $cc, $user, $maxSize = '4500k', $extra_msg = '') { $dfs = $objref->attributeref('crs_management_outgoing_document')->filesSize; $afs = $objref->attributeref('crs_management_outgoing_attachments')->filesSize; $body = array( ' !', ' ', '     .', easla.com '    '.$objref->description.($afs > 0 ? '  ' : '').'     '. $maxSize.'.', $extra_msg, ',       @sngp.ru', ' ', ' ,', $user->description ); $options = array( 'from'=>$user, 'to'=>$to, 'subj'=>$objref->description.' '.$objref->attributeref('crs_management_outgoing_subj')->value, 'body'=>implode('',$body), 'objects'=>$objref, 'files'=>array('maxSize'=>$maxSize) ); if (!empty($cc)) $options['cc'] = $cc; $options['bcc'] = $user; sendEmail($options); $rcvs = array(); foreach ($to as $e=>$d) $rcvs[] = corganization()->object($d)->viewLink().' ('.$e.')'; if (isset($cc)) foreach ($cc as $e=>$d) $rcvs[] = corganization()->object($d)->viewLink().' ('.$e.')'; $msg = '     : '.implode(',', $rcvs).'   : '.($dfs+$afs); } function sendFtpSngpRuAttachments(&$msg, $objref, $to, $cc, $user, $login, $password) { $ftp_addr = 'ftp.sngp.ru'; $ftp_link = ''; $dfs = $objref->attributeref('crs_management_outgoing_document')->filesSize; $afs = $objref->attributeref('crs_management_outgoing_attachments')->filesSize; if (cobjectref()->attributeref('crs_management_outgoing_attachments')->filesCount > 0) { $ftp = ftp_ssl_connect($ftp_addr); if ($ftp == FALSE) throw new Exception('    FTPS  '.$ftp_addr.'!'); ftp_login($ftp,$login,$password); ftp_pasv($ftp, true); $dir = "files"; if (!@ftp_chdir($ftp, $dir)) { ftp_mkdir($ftp, $dir); ftp_chdir($ftp, $dir); } $zip = normalizeFilename($objref->description).' - .zip'; $objref->attributeref('crs_management_outgoing_attachments')->ftp_put($ftp, $zip, FTP_BINARY); ftp_close($ftp); $ftp_link = ''; '.$zip.' } $files = array( 'attributeCodes'=>'crs_management_outgoing_document', 'compress'=>'zip', 'codepage'=>'CP866' ); $body = array( ' !', ' ', '     .', easla.com '    '.$objref->description.'  '.formatSize($dfs).'.', ($ftp_link == '' ? '' : '   FTPS  ftps://'.$ftp_addr.' [: '.$login.']  '.$ftp_link.'.'), ',       @sngp.ru', ' ', ' ,', $user->description ); $options = array( 'from'=>$user, 'to'=>$to, 'subj'=>$objref->description.' '.$objref->attributeref('crs_management_outgoing_subj')->value, 'body'=>implode('',$body), 'objects'=>$objref, 'files'=>$files ); if (!empty($cc)) $options['cc'] = $cc; $options['bcc'] = $user; sendEmail($options); $rcvs = array(); foreach ($to as $e=>$d) $rcvs[] = corganization()->object($d)->viewLink().' ('.$e.')'; if (isset($cc)) foreach ($cc as $e=>$d) $rcvs[] = corganization()->object($d)->viewLink().' ('.$e.')'; $msg = '   : '.implode(',', $rcvs).'  : '.($dfs).'    '.$ftp_addr.' [: '.$login.'].'; } if (empty(cobjectref()->attributeref('crs_management_outgoing_contragent')->value)) throw new Exception('    ()   '.cobjectref()->viewLink()); if (empty(cobjectref()->attributeref('crs_management_outgoing_contact')->value)) throw new Exception('    ()   '.cobjectref()->viewLink()); //define $to $contragent = select(cobjectref()->attributeref('crs_management_outgoing_contragent')->value); $to = contragentEmail($contragent); if ($to === false) throw new Exception('  .   '.$contragent->viewLink().'   '.cobjectref()->viewLink()); $to = array($to => $contragent->description); //define $cc $cc = array(); $contact = select(cobjectref()->attributeref('crs_management_outgoing_contact')->value); $tmp = getEmail($contact); if ($tmp === false) throw new Exception('  .   '.$contact->viewLink().'   '.cobjectref()->viewLink()); $cc += $tmp; if (!empty(cobjectref()->attributeref('crs_management_outgoing_recipients')->value)) { $contacts = selects(cobjectref()->attributeref('crs_management_outgoing_recipients')->value); foreach ($contacts as $contact) { $tmp = getEmail($contact); if ($tmp === false) throw new Exception('  .   '.$contact->viewLink().'   '.cobjectref()->viewLink()); $cc += $tmp; } } if (!empty(cobjectref()->attributeref('crs_management_outgoing_notifiers')->value)) { $contacts = selects(cobjectref()->attributeref('crs_management_outgoing_notifiers')->value); foreach ($contacts as $contact) { $tmp = getEmail($contact); if ($tmp === false) throw new Exception('  .   '.$contact->viewLink().'   '.cobjectref()->viewLink()); $cc += $tmp; } } if (empty($cc)) $cc = null; $cuser = cuser(); $rule = ''; if (cobjectref()->hasAttributeref('crs_management_outgoing_rule')) $rule = classificator(cobjectref()->crs_management_outgoing_rule)->code; $link = ''; if (cobjectref()->hasAttributeref('crs_management_outgoing_attachments_link')) { if (!empty(cobjectref()->attributeref('crs_management_outgoing_attachments_link')->value)) $link = '   .   .';  } $msg = ''; switch ($rule) { case 'crs_outgoing_rule_all_max5': sendEmailDocumentAndAttachments($msg, cobjectref(), $to, $cc, $cuser, 5120000, '4500k', $link); break; case 'crs_outgoing_rule_all_max4': sendEmailDocumentAndAttachments($msg, cobjectref(), $to, $cc, $cuser, 4096000, '3500k', $link); break; case 'crs_outgoing_rule_all_max3': sendEmailDocumentAndAttachments($msg, cobjectref(), $to, $cc, $cuser, 3072000, '2500k', $link); break; case 'crs_outgoing_rule_all_max10': sendEmailDocumentAndAttachments($msg, cobjectref(), $to, $cc, $cuser, 10240000, '9500k', $link); break; case 'crs_outgoing_rule_all_max25': sendEmailDocumentAndAttachments($msg, cobjectref(), $to, $cc, $cuser, 25600000, '20m', $link); break; case 'crs_outgoing_rule_doc_att_max5': if (empty($cc)) sendEmailDocumentAndAttachments($msg, cobjectref(), $to, null, $cuser, 5120000, '4500k', $link); else { $m1 = ''; $m2 = ''; sendEmailDocumentOnly($m1, cobjectref(), $to, null, $cuser, 5120000, '4500k', $link); sendEmailDocumentAndAttachments($m2, cobjectref(), $cc, null, $cuser, 5120000, '4500k', $link); $msg = $m1.' '.$m2; unset($m1,$m2); } break; case 'crs_outgoing_rule_doc_att_max25': if (empty($cc)) sendEmailDocumentAndAttachments($msg, cobjectref(), $to, null, $cuser, 25600000, '20m', $link); else { $m1 = ''; $m2 = ''; sendEmailDocumentOnly($m1, cobjectref(), $to, null, $cuser, 25600000, '20m', $link); sendEmailDocumentAndAttachments($m2, cobjectref(), $cc, null, $cuser, 25600000, '20m', $link); $msg = $m1.' '.$m2; unset($m1,$m2); } break; case 'crs_outgoing_rule_all_share': sendShareDocumentAndAttachments($msg, cobjectref(), $to, $cc, $cuser, $link); break; case 'crs_outgoing_rule_doc_att_share': if (empty($cc)) { if (empty(cobjectref()->attributeref('crs_management_outgoing_attachments')->value)) sendEmailDocumentOnly($msg, cobjectref(), $to, null, $cuser, 25600000, '20m', $link); else sendShareDocumentAndAttachments($msg, cobjectref(), $to, null, $cuser, $link); } else { $m1 = ''; $m2 = ''; sendEmailDocumentOnly($m1, cobjectref(), $to, null, $cuser, 25600000, '20m', $link); sendShareDocumentAndAttachments($m2, cobjectref(), $cc, null, $cuser, $link); $msg = $m1.' '.$m2; unset($m1,$m2); } break; case 'crs_outgoing_rule_doc_att_share_max1': $sum = cobjectref()->attributeref('crs_management_outgoing_document')->filesSize + cobjectref()->attributeref('crs_management_outgoing_attachments')->filesSize; if ($sum < 1048576) { sendAsIsDocumentAndAttachments($msg, cobjectref(), $to, $cc, $cuser, '1m', $link); } else { $m1 = ''; $m2 = ''; shareAttachments($m2, cobjectref(), $cuser); sendEmailDocumentOnly($msg, cobjectref(), $to, $cc, $cuser, 25600000, '20m', $m2.''.$link); } break; case 'crs_outgoing_rule_asis_max3': sendAsIsDocumentAndAttachments($msg, cobjectref(), $to, $cc, $cuser, '3m', $link); break; case 'crs_outgoing_rule_asis_max5': sendAsIsDocumentAndAttachments($msg, cobjectref(), $to, $cc, $cuser, '5m', $link); break; case 'crs_outgoing_rule_asis_max10': sendAsIsDocumentAndAttachments($msg, cobjectref(), $to, $cc, $cuser, '10m', $link); break; case 'crs_outgoing_rule_asis_max20': sendAsIsDocumentAndAttachments($msg, cobjectref(), $to, $cc, $cuser, '20m', $link); break; case 'crs_outgoing_rule_ftp_sngp_ru_tyungd': sendFtpSngpRuAttachments($msg, cobjectref(), $to, $cc, $cuser, ' ', ' '); break; default: sendEmailDocumentAndAttachments($msg, cobjectref(), $to, $cc, $cuser, $link); } if (empty(cobjectref()->attributeref('crs_management_outgoing_sentdate')->value)) { cobjectref()->attributeref('crs_management_outgoing_sentdate')->value = currentDateTime(); cobjectref()->description = cobjectref()->calcOutgoingDesc(); cobjectref()->updateDocumentFileName(); } cobjectref()->status = 'crs_management_outgoing_email'; $rcvs = array(); foreach ($to as $e=>$d) $rcvs[] = corganization()->object($d)->viewLink().' ('.$e.')'; if (isset($cc)) foreach ($cc as $e=>$d) $rcvs[] = corganization()->object($d)->viewLink().' ('.$e.')'; //  $notices = array(); $ruser = corganization()->user(cobjectref()->crs_management_outgoing_responsibleuser); $rgroups = $ruser->groups(); if (empty($rgroups)) $notices[] = $ruser; else { $f = false; foreach($rgroups as $rgroup) if (strncmp($rgroup['data_one'],'09.',3) == 0) { $notices = $rgroup->users(); $f = true; break; } if (!$f) $notices[] = $ruser; } if (!empty(cobjectref()->crs_management_outgoing_performers)) { $eusers = corganization()->users(cobjectref()->crs_management_outgoing_performers); if (!empty($eusers)) $notices = array_merge($notices, $eusers); } $notices = array_unique($notices, SORT_REGULAR); if (!empty(cobjectref()->crs_management_outgoing_contract)) { $contracts = selects(cobjectref()->crs_management_outgoing_contract); foreach($contracts as &$c) { $c = $c->viewLink(); } } else { $contracts = array(); } foreach($notices as $n) { $rbody = array( ' '.$n->description.'!', ' ', ': '.cobjectref()->viewLink(), ': '.cobjectref()->crs_management_outgoing_subj, ':'.implode(', ', $contracts), '  :', implode('', $rcvs), ' ', ' ,', cuser()->description ); sendEmail(array( // 'from'=>cuser(), 'to'=>$n, 'subj'=>'   '.cobjectref()->description, 'body'=>implode('',$rbody), )); } $task_msg = cobjectref()->commentTasks(); if (!empty($task_msg)) $msg .= ''.implode('',$task_msg); echo $msg; //   caction()->result = $msg; 


sendMail. , , zip , 7zip .
. :



. , , , .. .



. ! , , , - !
. , . easla.com , !
PS . « » . , .

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


All Articles