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

В 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 пишет:

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

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