Расширение функционала “Утверждение документов”

В Microsoft Dynamics NAV 5.0 появился новый механизм – утверждение документа. Он используется в тех случаях, когда перед тем как можно будет запустить документ в работу, его должен утвердить другой сотрудник компании. Указанный функционал используется для документов покупки и продажи, в данной статье будет показано как включить в него новые документы.

Р’ нашем примере РјС‹ будем утверждать Утвержденный Производственный Заказ перед изменением статуса (Функции, Р?зменить Статус).

В первую очередь надо создать Шаблон утверждения (Администрирование, Настройка Приложения, Утверждение документа, Шаблоны утверждения).
Для этого, предварительно создадим Код утверждения:

Код= ЗАП-ПР-ЗАКАЗ, Связано с таблице (номер) = 5405.

Теперь создадим сами шаблоны утверждения:

  • РљРѕРґ утверждения = Р—РђРџ-РџР -Р—РђРљРђР—, РўРёРї утверждения = [пусто], РўРёРї документа = Счет, РўРёРї лимита = Нет лимитов
  • РљРѕРґ утверждения = Р—РђРџ-РџР -Р—РђРљРђР—, РўРёРї утверждения = Утверждающий, РўРёРї документа = Счет, РўРёРї лимита = Нет лимитов


Пояснения.
Тип документа = Счет – это поле Option со значением 2, именно это значение соответствует статусу Утвержден для производственного заказа.
В данном примере мы не будем анализировать суммы производственного заказа и прочие условия, поэтому Тип лимита всегда должен быть Нет лимитов. Возможно, тему анализа сумм мы раскроем в следующей статье, но не факт.
В производственном заказе нет поля Менеджер/Закупщик, поэтому нет смысла использовать Тип утверждения = Продавец/Покупатель. Для Тип утверждения = [пусто], следует задать Дополнительных утверждающих лиц. Для Тип утверждения = Утверждающий, нужно выполнить Настройку Утверждений Пользователя.

Шаблон создан, теперь перед вызовом функции по изменению Статуса утвержденного производственного заказа нужно вставить проверку: требуется ли для данного документа утверждение.Функция для проверки находится в кодеюните 439 Approvals Management, нам она не подходит, т.к. в нее передаются два параметра типа запись, один для документов покупки, другой для документов продажи.
Придется создать свою функцию. Но не будем спешить.

Утверждение документов базируется на программном изменении поле Статус: Открыт – Ожидает Утверждения – Выпущен.
В производственном заказе такого поля нет. Его надо создать.

В таблице 5405 Production Order добавить поле 50005 Status2 типа Option:Open,Released,Pending Approval; Editable = No.
Так как слово Status уже занято, пришлось использовать Status2. К сожалению это добавит путаницы.

Теперь добавим требуемые функции в кодеюнит 439.

PreStatusApprovalCheck(ProdOrderHeader : Record "Production Order") : Boolean 
IF ProdOrderHeader."No." [не равно] '' THEN BEGIN 
В  IF ProdOrderHeader.Status2 = ProdOrderHeader.Status2::"Pending Approval" THEN BEGIN 
  В  ERROR(Text013, ProdOrderHeader."No."); 
В  END ELSE BEGIN 
  В  IF NOT CheckApprDocument(DATABASE::"Production Order",ProdOrderHeader.Status) THEN 
    В  EXIT(TRUE) 
  В  ELSE BEGIN 
    В  IF NOT (ProdOrderHeader.Status2 = ProdOrderHeader.Status2::Released) THEN 
      В  ERROR(Text013, ProdOrderHeader."No.") 
   В   ELSE 
     В   EXIT(TRUE); 
  В  END; 
В  END; 
END                 
 
CheckApprDocument(pTableNo : Integer;pDocumentType : Option) : Boolean 
ApprovalTemplate.SETCURRENTKEY("Table ID","Document Type",Enabled); 
ApprovalTemplate.SETRANGE("Table ID",pTableNo); 
ApprovalTemplate.SETRANGE("Document Type",pDocumentType); 
ApprovalTemplate.SETRANGE(Enabled,TRUE); 
IF ApprovalTemplate.FIND('-') THEN 
  EXIT(TRUE) 
ELSE 
В  EXIT(FALSE);

Для документов покупки и документов продажи использовались две одинаковые функции, здесь я не выдержал и все же написал универсальную CheckApprDocument, где вместо переменной типа Запись передаются Номер таблицы и Тип документа. В данном случае это (5405,2), что значит (Производственный Заказ,Утвержден).

Теперь изменим форму Утвержденного Производственного Заказа так, чтобы перед тем как вызывалась функция по изменению статуса, выполнялась проверка на утверждение.

Форма 99000829 Firm Planned Prod. Order, menu button - Функции, menu item – Р?зменить Статус.
Свойства: PushAction = , RunObject = [пусто]

OnPush() 
// ApprovalMgt – codeunit 439 Approvals Management 
IF ApprovalMgt.PreStatusApprovalCheck(Rec) THEN 
В  CODEUNIT.RUN(CODEUNIT::"Prod. Order Status Management",Rec);

Сохраните форму и попробуйте изменить статус документа (соответствующие шаблоны должны быть активированы, утверждающие лица заданы).

Отлично. Первый этап выполнен – изменить статус документу без утверждения нельзя.

Теперь перейдем собственно к полю Status2. Т.к. оно не редактируется, для его изменения потребуется специальный кодеюнит. Создадим такой, взяв за образец 414 Release Sales Document или 415 Release Purchase Document.

Codeunit 50001 Release Prod. Order

OnRun(VAR Rec : Record "Production Order") 
IF Status2 = Status2::Released THEN 
В  EXIT;                 
 
//Если требуется здесь можно вставить проверку документа и его строк. 
//ERROR(Text001,"Status","No.");                 
 
Status2 := Status2::Released;                 
 
MODIFY(TRUE);                 
 
Reopen(VAR ProdOrderHeader : Record "Production Order")                 
 
WITH ProdOrderHeader DO BEGIN 
В  IF Status2 = Status2::Open THEN 
  В  EXIT; 
  Status2 := Status2::Open; 
В  MODIFY(TRUE); 
END;                 
 
PerformManualRelease(VAR ProdOrderHeader : Record "Production Order") 
//ApprovalEntry - Record.Approval Entry 
//ApprovalManagement - Codeunit.Approvals Management 
//ApprovedOnly - Boolean 
WITH ProdOrderHeader DO BEGIN 
В  IF ApprovalManagement.CheckApprDocument(DATABASE::"Production Order",ProdOrderHeader.Status) THEN BEGIN 
  В  CASE Status2 OF 
    В  Status2::"Pending Approval", Status2::"Open",: 
      В  ERROR(Text002); 
    В  Status2::Released: 
      В  CODEUNIT.RUN(50001,ProdOrderHeader); 
В    END; 
В  END ELSE 
  В  CODEUNIT.RUN(50001,ProdOrderHeader); 
END;                 
 
PerformManualReopen(VAR ProdOrderHeader : Record "Production Order") 
//ApprovalManagement Codeunit.Approvals Management 
WITH ProdOrderHeader DO BEGIN 
В  IF ApprovalManagement.CheckApprDocument(DATABASE::"Production Order",ProdOrderHeader.Status) THEN BEGIN 
  В  CASE Status2 OF 
    В  Status2::Open,Status2::Released: 
      В  Reopen(ProdOrderHeader); 
  В  END; 
В  END ELSE 
  В  Reopen(ProdOrderHeader); 
END;

Теперь добавим поле Status2 на форму утвержденного производственного заказа, а также функции по изменению статуса.

Форма 99000829 Firm Planned Prod. Order, добавить поле Status2.
В menu button – Функции добавить для menu item
Выпустить CTRL+F11

OnPush() 
//ReleaseProdOrder – наш новый кодеюнит 50001 Release Prod. Order 
ReleaseProdOrder.PerformManualRelease(Rec);

Открыть

OnPush() 
// ReleaseProdOrder – наш новый кодеюнит 50001 Release Prod. Order 
ReleaseProdOrder.PerformManualReopen(Rec);

Теперь можно менять статус документа вручную.
С активизированным шаблоном на утверждение результат будет прежним – требование утвердить документ.
Перейдем к созданию запроса на утверждение. Отправляемся в кодеюнит 439 Approvals Management.
Создадим в нем две функции по созданию запроса на утверждение:
Первая.

SendProdOrderApprovalRequest(VAR ProdOrderHeader : Record "Production Order") : Boolean 
TestSetup; 
WITH ProdOrderHeader DO BEGIN 
  IF Status2 [не равно] Status2::Open THEN 
  В  EXIT(FALSE); 
  IF NOT ApprovalSetup.GET THEN 
В    ERROR(Text004); 
  TemplateRec.SETCURRENTKEY("Table ID","Document Type",Enabled); 
В  TemplateRec.SETRANGE("Table ID",DATABASE::"Production Order"); 
В  TemplateRec.SETRANGE("Document Type",Status); 
В  TemplateRec.SETRANGE(Enabled,TRUE); 
В  IF TemplateRec.FIND('-') THEN BEGIN 
  В  REPEAT 
      //проверки на типы лимитов и утверждающего лица. 
    В  TemplateRec.TESTFIELD("Limit Type",TemplateRec."Limit Type"::"No Limits"); 
    В  IF TemplateRec."Approval Type"=TemplateRec."Approval Type"::"Sales Pers./Purchaser" THEN 
      В  TemplateRec.FIELDERROR("Approval Type"); 
      IF NOT FindApproverProdOrder(ProdOrderHeader,ApprovalSetup,TemplateRec) THEN 
      В  ERROR(Text010); 
    UNTIL TemplateRec.NEXT = 0; 
  В  IF DispMessage THEN 
  В    MESSAGE(Text001,FORMAT(Status),"No."); 
  END ELSE 
  В  ERROR(STRSUBSTNO(Text129,ProdOrderHeader.Status)); 
END;

В данной функции выполняются проверки на корректность создания шаблона. А затем вызывается вторая функция, в которой создаются запросы на утверждение согласно Настройкам Утверждений Пользователей и Дополнительных Утверждающих Лиц.

FindApproverProdOrder(ProdOrderHeader : Record "Production Order";ApprovalSetup : Record "Approval Setup";AppTemplate : Record "Approval Templates"):Boolean 
ApprovaAddApproversTemp.RESET; 
AddApproversTemp.DELETEALL; 
CASE AppTemplate."Approval Type" OF 
В  AppTemplate."Approval Type"::"Sales Pers./Purchaser": BEGIN 
    //Не используется. См. условие. 
В  END;                 
 
  AppTemplate."Approval Type"::Approver: BEGIN 
  В  UserSetup.SETRANGE("User ID",USERID); 
  В  IF NOT UserSetup.FIND('-') THEN 
    В  ERROR(Text005,USERID); 
    CASE AppTemplate."Limit Type" OF 
    В  AppTemplate."Limit Type"::"Approval Limits": BEGIN 
        //Не используется. См. условие. 
  В    END; 
      AppTemplate."Limit Type"::"Request Limits": BEGIN 
        //Не используется. См. условие. 
    В  END; 
      AppTemplate."Limit Type"::"No Limits": BEGIN 
      В  ApproverId := UserSetup."Approver ID"; 
      В  IF ApproverId = '' THEN 
        В  ApproverId := UserSetup."User ID"; 
        MakeApprovalEntry( 
        В  DATABASE::"Production Order",ProdOrderHeader.Status,ProdOrderHeader."No.",'', 
        В  ApprovalSetup,ApproverId,AppTemplate."Approval Code", 
        В  UserSetup,0,0,'',AppTemplate,0);                 
 
        CheckAddApprovers(AppTemplate); 
      В  IF AddApproversTemp.FIND('-') THEN 
          REPEAT 
        В    ApproverId := AddApproversTemp."Approver ID"; 
            MakeApprovalEntry( 
            В  DATABASE::"Production Order",ProdOrderHeader.Status,ProdOrderHeader."No.",'', 
            В  ApprovalSetup,ApproverId,AppTemplate."Approval Code", 
            В  UserSetup,0,0,'',AppTemplate,0); 
          UNTIL AddApproversTemp.NEXT = 0; 
      END; 
    END; 
В  END;                 
 
  AppTemplate."Approval Type"::" ": BEGIN 
  В  CheckAddApprovers(AppTemplate); 
  В  IF AddApproversTemp.FIND('-') THEN 
    В  REPEAT 
      В  ApproverId := AddApproversTemp."Approver ID"; 
      В  MakeApprovalEntry( 
        В  DATABASE::"Production Order",ProdOrderHeader.Status,ProdOrderHeader."No.",'', 
        В  ApprovalSetup,ApproverId,AppTemplate."Approval Code", 
        В  UserSetup,0,0,'',AppTemplate,0); 
      UNTIL AddApproversTemp.NEXT = 0 
  В  ELSE 
     В ERROR(Text027); 
  END; 
END; 
EntryApproved := FALSE; 
DocReleased := FALSE; 
WITH ApprovalEntry DO BEGIN 
В  INIT; 
В  SETRANGE("Table ID",DATABASE::"Production Order"); 
В  SETRANGE("Document Type",ProdOrderHeader.Status); 
В  SETRANGE("Document No.",ProdOrderHeader."No."); 
В  SETRANGE(Status,Status::Created); 
В  IF FINDSET(TRUE,FALSE) THEN 
  В  REPEAT 
    В  IF "Sender ID" = "Approver ID" THEN BEGIN 
      В  Status := Status::Approved; 
      В  MODIFY; 
    В  END ELSE 
     В  IF NOT IsOpenStatusSet THEN BEGIN 
       В  Status := Status::Open; 
       В  MODIFY; 
       В  IsOpenStatusSet := TRUE; 
         // IF ApprovalSetup.Approvals THEN 
         // Отправка уведомлений по электронной почте 
    В   END; 
В    UNTIL NEXT = 0; 
  В  SETFILTER(Status,'=%1|%2|%3',Status::Approved,Status::Created,Status::Open); 
    IF FIND('-') THEN 
    В  REPEAT 
      В  IF Status = Status::Approved THEN 
          EntryApproved := TRUE 
      В  ELSE 
        В  EntryApproved := FALSE; 
      UNTIL NEXT = 0; 
    IF EntryApproved THEN 
     В DocReleased := ApproveApprovalRequest(ApprovalEntry); 
  В  DispMessage := FALSE; 
  В  IF NOT DocReleased THEN BEGIN 
      ProdOrderHeader.Status2 := ProdOrderHeader.Status2::"Pending Approval"; 
  В  ProdOrderHeader.MODIFY(TRUE); 
  В  DispMessage := TRUE; 
В  END;                 
 
В  IF DocReleased THEN 
  В  MESSAGE(Text003,ProdOrderHeader.Status,ProdOrderHeader."No."); 
В  EXIT(TRUE); 
END;

В данной статье рассылка уведомлений по электронной почте не рассматривается. Однако выделены места, куда нужно вставить соответствующий код, подсмотрев его в аналогичных функциях.

Также требуется модифицировать функцию
ApproveApprovalRequest(ApprovalEntry : Record “Approval Entry”) : Boolean
Где вместо фрагмента

IF ApprovalEntry."Table ID" = DATABASE::"Sales Header" THEN BEGINВ  
  IF SalesHeader.GET(ApprovalEntry."Document Type",ApprovalEntry."Document No.") THEN 
В  В  ReleaseSalesDoc.RUN(SalesHeader); 
END ELSE 
В  IF PurchaseHeader.GET(ApprovalEntry."Document Type",ApprovalEntry."Document No.") THEN 
В    ReleasePurchaseDoc.RUN(PurchaseHeader);

Р?спользовать РєРѕРґ

CASE ApprovalEntry."Table ID" OFВ  
  DATABASE::"Sales Header": 
В  В  IF SalesHeader.GET(ApprovalEntry."Document Type",ApprovalEntry."Document No.") THEN 
В      ReleaseSalesDoc.RUN(SalesHeader); 
В  DATABASE::"Purchase Header": 
В    IF PurchaseHeader.GET(ApprovalEntry."Document Type",ApprovalEntry."Document No.") THEN 
В      ReleasePurchaseDoc.RUN(PurchaseHeader); 
В  DATABASE::"Production Order": 
В  В  IF ProdOrderHeader.GET(ApprovalEntry."Document Type",ApprovalEntry."Document No.") THEN 
В      ReleaseProdOrder.RUN(ProdOrderHeader); 
END;

А теперь добавим функцию, отвечающую за отмену запроса на утверждение

CancelProdOrderApprovalRequest(VAR ProdOrderHeader : Record "Production Order";ShowMessage : Boolean;ManualCancel : Boolean) : Boolean 
TestSetup; 
IF ProdOrderHeader.Status2 = ProdOrderHeader.Status2::Open THEN 
В  EXIT; 
IF NOT ApprovalSetup.GET THEN 
В  ERROR(Text004); 
WITH ProdOrderHeader DO BEGIN 
В  ApprovalEntry.SETCURRENTKEY("Table ID","Document Type","Document No.","Sequence No."); 
В  ApprovalEntry.SETRANGE("Table ID",DATABASE::"Production Order"); 
В  ApprovalEntry.SETRANGE("Document Type",Status); 
В  ApprovalEntry.SETRANGE("Document No.","No."); 
  ApprovalEntry.SETFILTER(Status,'[не равно]%1[и не равно]%2',ApprovalEntry.Status::Rejected,ApprovalEntry.Status::Canceled); 
В  SendMail := FALSE;                 
 
В  IF ApprovalEntry.FIND('-') THEN BEGIN 
  В  REPEAT 
    В  IF (ApprovalEntry.Status = ApprovalEntry.Status::Open) OR 
      В  (ApprovalEntry.Status = ApprovalEntry.Status::Approved) THEN 
    В    SendMail := TRUE; 
    В  ApprovalEntry.Status := ApprovalEntry.Status::Canceled; 
    В  ApprovalEntry."Last Date-Time Modified" := CREATEDATETIME(TODAY,TIME); 
    В  ApprovalEntry."Last Modified By ID" := USERID; 
    В  ApprovalEntry.MODIFY; 
      IF ApprovalSetup.Cancellations AND ShowMessage AND SendMail THEN BEGIN 
        // Отправка уведомлений по электронной почте 
        // MailCreated := TRUE; 
      В  SendMail := FALSE; 
      END; 
  В  UNTIL ApprovalEntry.NEXT = 0; 
  В  IF MailCreated THEN BEGIN 
    В  AppManagement.SendMail; 
    В  MailCreated := FALSE; 
  В  END; 
В  END;                 
 
IF ManualCancel OR (NOT ManualCancel AND NOT (Status2 = Status2::Released)) THEN 
В  Status2 := Status2::Open; 
В  MODIFY(TRUE); 
END;                 
 
IF ShowMessage THEN 
В  MESSAGE(Text002,ProdOrderHeader.Status,ProdOrderHeader."No.");

Ну и добавим соответствующие функции на форму 99000829 Firm Planned Prod. Order,
В menu button – Функции добавить для menu item
Отправить запрос на утверждение

OnPush() 
IF ApprovalMgt.SendProdOrderApprovalRequest(Rec) THEN;

Отменить запрос на утверждение

OnPush() 
IF ApprovalMgt.CancelProdOrderApprovalRequest(Rec,TRUE,TRUE) THEN;

где ApprovalMgt – кодеюнит Approvals Management

Теперь запрос на утверждение уже формируется, а также может быть отозван инициатором.

В функцию ApproveApprovalRequest уже внесены изменения, теперь изменим RejectApprovalRequest. Заменим код

IF ApprovalEntry."Table ID" = DATABASE::"Sales Header" THEN BEGIN 
В  SalesHeader.SETCURRENTKEY("Document Type","No."); 
В  SalesHeader.SETRANGE("Document Type",ApprovalEntry."Document Type"); 
В  SalesHeader.SETRANGE("No.",ApprovalEntry."Document No."); 
В  IF SalesHeader.FIND('-') THEN 
  В  ReleaseSalesDoc.Reopen(SalesHeader); 
END ELSE BEGIN 
В  PurchaseHeader.SETCURRENTKEY("Document Type","No."); 
В  PurchaseHeader.SETRANGE("Document Type",ApprovalEntry."Document Type"); 
В  PurchaseHeader.SETRANGE("No.",ApprovalEntry."Document No."); 
В  IF PurchaseHeader.FIND('-') THEN 
  В  ReleasePurchaseDoc.Reopen(PurchaseHeader); 
END;

Следующим кодом

CASE ApprovalEntry."Table ID" OFВ  
  DATABASE::"Sales Header": BEGIN 
  В  SalesHeader.SETCURRENTKEY("Document Type","No."); 
  В  SalesHeader.SETRANGE("Document Type",ApprovalEntry."Document Type"); 
  В  SalesHeader.SETRANGE("No.",ApprovalEntry."Document No."); 
  В  IF SalesHeader.FIND('-') THEN 
    В  ReleaseSalesDoc.Reopen(SalesHeader); 
В  END; 
В  DATABASE::"Purchase Header": BEGIN 
  В  PurchaseHeader.SETCURRENTKEY("Document Type","No."); 
  В  PurchaseHeader.SETRANGE("Document Type",ApprovalEntry."Document Type"); 
  В  PurchaseHeader.SETRANGE("No.",ApprovalEntry."Document No."); 
  В  IF PurchaseHeader.FIND('-') THEN 
    В  ReleasePurchaseDoc.Reopen(PurchaseHeader); 
В  END; 
В  DATABASE::"Production Order": BEGIN 
  В  ProdOrderHeader.SETCURRENTKEY(Status,"No."); 
  В  ProdOrderHeader.SETRANGE(Status,ApprovalEntry."Document Type"); 
  В  ProdOrderHeader.SETRANGE("No.",ApprovalEntry."Document No."); 
  В  IF ProdOrderHeader.FIND('-') THEN 
    В  ReleaseProdOrder.Reopen(ProdOrderHeader); 
В  END; 
END;

Функция по делегированию изменения не требует. А вот в функцию Показать, Документ вызываемую из окон Операции Утверждения и Операции Утверждения Запросов нужно включить поддержку отображения Утвержденных Производственных Заказов. Для этого требуется изменить функцию ShowDocument() таблицы 454 Approval Entry.
В нее требуется добавить следующий фрагмент:

DATABASE::"Production Order":В  BEGIN 
В  IF NOT ProdOrderHeader.GET("Document Type","Document No.") THEN 
  В  EXIT; 
В  CASE "Document Type" OF 
  В  "Document Type"::Invoice: 
    В  FORM.RUN(FORM::"Firm Planned Prod. Order",ProdOrderHeader); 
В  END; 
END;

Куда вставлять надо догадаться самому, это не трудно.

Подсказка – перед кодом:

В  ELSE 
В  В  EXIT; 
END;

Что еще.
Надо в строки производственного заказа вставить запрет на редактирование документов со статусом Выпущен и Ожидает Утверждения.
Вставить код по отправке утверждений по электронной почте в функции:

  • ApproveApprovalRequest(ApprovalEntry : Record “Approval Entry”) : Boolean
  • DelegateApprovalRequest(ApprovalEntry : Record “Approval Entry”)
  • SendRejectionMail(ApprovalEntry : Record “Approval Entry”;AppManagement : Codeunit “Approvals Mgt Notification”)

Примечание. РџСЂРё наборе статьи возникли проблемы СЃ символами <, > Рё &. Поэтому вместо “<>” используется [РЅРµ равно], Р° вместо “&<>” [Рё РЅРµ равно]

Метки:



Комментариев: 1

  1. Manryct пишет:

    Спасибо за статью оказалась очень полезной.

Оставьте свой отзыв!