Расширение функционала “Утверждение документов”
В 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 =
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 Ноябрь 2008 в 12:51
Спасибо за статью оказалась очень полезной.