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