“請假管理”應(yīng)用,應(yīng)該算是 SharePoint 的“Hello World!”、川菜里面的魚香肉絲、粵菜里面的蛋炒飯 。。。吧?
怎么樣才能做出簡易、實用的請假管理,一直都是都是一個問題。完全 code free 不寫代碼是搞不出來的,完全寫代碼實現(xiàn)的話又何必用 SharePoint?簡潔、輕快的解決方案才是我們追求的。
問題
通常的“請假管理” SharePoint 實現(xiàn)存在這樣幾個問題:
權(quán)限。
要么就是所有人都可以看見你的請假單,要么就是只有你自己可以看到,要了命了。參見 這里 的描述。常見的解決方案,要么就是直接忽略這個問題,或者用視圖來過濾篩選,但這不是根本的辦法。
預(yù)先指定審批人員。
而實用的要求,其實是動態(tài)的指定審批人員。而且,往往并不是先由上級職能經(jīng)理審批,而是先由所在項目的項目經(jīng)理審批、職能經(jīng)理一般只要項目經(jīng)理沒意見都會同意的。
年假天數(shù)約束。
假別里面如果是年假,則應(yīng)該有天數(shù)的限制,且每年重置。軟件應(yīng)當(dāng)自動對剩余年假天數(shù)做核對,避免需要人工再去查年假天數(shù)。
孤立。
沒有和 SharePoint 其它應(yīng)用配合,比如項目、日歷。
目標(biāo)
實用的“請假管理”應(yīng)該什么樣子呢?
1、所有人都可以提出請假申請。如下圖所示:
2、自動查找合適的審批人。在項目中則項目經(jīng)理就是審批人(直接去項目列表中找“項目經(jīng)理”字段對應(yīng)的用戶),否則就是職能部門經(jīng)理(如下圖所示)。
3、不能申請超出可用年假天數(shù)的年假。
4、但是,一旦提交申請,就只有本人、審批人、管理員可以看到。而且審批人有“批準(zhǔn)”權(quán)限。
注意:SharePoint 只允許每個列表擁有 8000 個獨立權(quán)限的列表項。所以,后面要配合列表的信息管理策略將完成的請假單轉(zhuǎn)移到別的地方。
5、申請人發(fā)起請假流程。
6、審批人審批請假申請。
7、審批完成后不能再修改申請。
配置信息管理策略將“聲明為記錄”的項在1個月后移動到其它的文檔庫,避免擁有獨立權(quán)限的項目超過 SharePoint 對每個列表 8000 條的限制。
8、如果同意了年假申請,那么,自動從當(dāng)年年假中扣除。
9、申請同意后自動加入請假日歷。
該日歷可以和其它日歷合并在內(nèi)網(wǎng)門戶上顯示。
實現(xiàn)
說實話,實現(xiàn)起來并不簡單。但是,通過努力,可以保持解決方案的干凈、輕快,與整個架構(gòu)體系融合在一起。
1、所有人都可以提出請假申請。
直接斷開“請假單”列表的權(quán)限繼承,為所有用戶設(shè)置“參與討論”權(quán)限級別即可。
具體操作參見這里 中斷列表或庫的權(quán)限繼承。
2、自動查找合適的審批人。
開發(fā)自定義字段,加入“請假單”列表。
此自定義字段將獲取申請人所在項目的項目經(jīng)理或者申請人的上級職能經(jīng)理。
創(chuàng)建自定義字段類型的文章在這里 創(chuàng)建自定義 SharePoint 2010 字段類型。
獲取當(dāng)前用戶的上級職能經(jīng)理。需要用到 UserProfileManager 對象。
UserProfileManager upm = new UserProfileManager(SPServiceContext.Current);
if (upm.UserExists(user.LoginName))
{
UserProfile u = upm.GetUserProfile(user.LoginName);
UserProfile[] managers = u.GetManagers();
if (managers != null)
{
foreach(UserProfile manager in managers){
SPUser u_manager = web.SiteUsers[manager[PropertyConstants.AccountName].Value.ToString()];
// 其它自定義代碼。
}
}
}
3、不能申請超出可用年假天數(shù)的年假。
這里需要用 SharePoint Designer 修改“請假單”列表的“NewForm.aspx”文件,利用 JavaScript 腳本調(diào)用 SharePoint 的 Client Object Model 獲取剩余年假并顯示在界面上。
首先,需要引入幾個 js 庫:jQuery 和 jQuery.SPServices。jQuery 已經(jīng)放入 masterpage。
<SharePoint:ScriptLink Name="SP.js" runat="server" OnDemand="true" Localizable="false" /> <script src="http://www.cnblogs.com/DocLib/spservices/jquery.SPServices-0.7.1a.min.js" type="text/javascript"></script>
然后,在選擇假別的下拉框內(nèi)容改變時,讀取可用年假天數(shù)。 (我當(dāng)時為什么要用 lj 做變量名?我也很奇怪。)
$('select[title="假別"]').change(function(){ var lj=$(this).val(); if(lj=='年假'){ ExecuteOrDelayUntilScriptLoaded(get_annual_leave_days, "sp.js"); } else{ $("nobr:contains('請假天數(shù)')").children().each(function(){ $(this).html("*"); }); } });
get_annual_leave_days 方法將讀取當(dāng)前用戶所剩余年假天數(shù)。下面函數(shù)中變量命名方法并不統(tǒng)一,這是這些代碼來自多個不同時期的不同項目的印記啊!軟件開發(fā)是個手藝活兒。
"_x5269__x4f59__x5e74__x5047__x59" 是字段“剩余年假天數(shù)”的 InnerName。
var _ctx = null; var _items = null; function get_annual_leave_days(){ _ctx = new SP.ClientContext.get_current(); var web = _ctx.get_web(); var lists = web.get_lists(); var list_annual_leave = lists.getByTitle("年假匯總"); var currentDate = new Date(); var year = currentDate.getFullYear(); var currentUserID = $().SPServices.SPGetCurrentUser({ fieldName: "ID", debug: false }); var camlQuery = new SP.CamlQuery(); var strCaml = "<View>" + "<Query>" + "<Where>"+ "<And>"+ "<Eq>"+ "<FieldRef Name='_x4eba__x5458_' LookupId='TRUE' />"+ "<Value Type='Lookup'>"+currentUserID+"</Value>"+ "</Eq>"+ "<Eq>"+ "<FieldRef Name='_x5e74__x4efd_' />"+ "<Value Type='Integer'>"+year+"</Value>"+ "</Eq>"+ "</And>"+ "</Where>"+ "</Query>" + "</View>"; camlQuery.set_viewXml(strCaml); this._items = list_annual_leave.getItems(camlQuery); _ctx.load(_items); _ctx.executeQueryAsync(Function.createDelegate(this, this.onSuccess), Function.createDelegate(this, this.onFail)); } function onSuccess(sender, args) { var listItemEnumerator = this._items.getEnumerator(); while(listItemEnumerator.moveNext()) { var oListItem = listItemEnumerator.get_current(); var days = oListItem.get_item("_x5269__x4f59__x5e74__x5047__x59"); $("nobr:contains('請假天數(shù)')").children().each(function(){ $(this).html("(剩余 "+days+" 天)*"); }); } } function onFail(sender, args) { alert('獲取年假天數(shù)時出錯:' + args.get_message()); }
更多的技術(shù)細節(jié)可以參考 ECMAScript 對象模型系列,這個系列講解很細致了。另外,JS 腳本調(diào)用 SharePoint 的 JSCOM 時是異步操作,回調(diào)次數(shù)多了代碼會很亂,這篇 使用Jscex增強SharePoint 2010 JavaScript Client Object Model (JSOM) 提供了一優(yōu)化代碼的解決方案可供參考。
4、但是,一旦提交申請,就只有本人、審批人、管理員可以看到。而且審批人有“批準(zhǔn)”權(quán)限。
為實現(xiàn)這個功能,需要處理列表的 Create 事件。
先斷開現(xiàn)有的繼承權(quán)限。
item.BreakRoleInheritance(false);
然后,綁定新的權(quán)限。
protected void bind_role(SPListItem item, SPPrincipal principal, SPRoleDefinition definition) { try { SPRoleAssignment assignment = new SPRoleAssignment(principal); assignment.RoleDefinitionBindings.Add(definition); item.RoleAssignments.Add(assignment); } catch (Exception ex) { throw ex; } }
對某個用戶執(zhí)行綁定角色的操作。
bind_role(item, user, web.RoleDefinitions["參與討論"]);
5、申請人發(fā)起請假流程。
用 SharePoint Designer 建立請假流程。
6、審批人審批請假申請。
審批即可。可以用 InfoPath Designer 從 SPD 中打開美化一下任務(wù)界面。
如果需要對任務(wù)也實施和“請假單”相同的權(quán)限控制,可以參考對“請假單”的處理一樣進行。
7、審批完成后不能再修改申請。
需要開發(fā)處理工作流事件的代碼,聲明當(dāng)前項目為記錄。
SPWorkflow wf = new SPWorkflow(web, properties.InstanceId); SPList list = web.Lists[wf.ListId]; if (list.Title == "請假單") { SPListItem item = list.Items.GetItemById(wf.ItemId); title = item.Title; if (!Records.IsRecord(item)) { Records.DeclareItemAsRecord(item); } }
還需要配置信息管理策略,自動備份已經(jīng)完成流程的“請假單”到歸檔庫中。
8、如果同意了年假申請,那么,自動從當(dāng)年年假中扣除。
同樣在工作流事件代碼中處理,扣除年假天數(shù)。做減法即可。由于申請人本人沒有直接修改年假天數(shù)的權(quán)限(這是當(dāng)然的),所以需要提升權(quán)限才能操作。而這也意味著要服務(wù)器場解決方案,“全程沙盒方案”夢想破滅。
9、申請同意后自動加入請假日歷。
在 SPD 工作流中處理。
10、簽入代碼和 SPD 文件(導(dǎo)出來備份,或者直接 stsadm –o backup 備份網(wǎng)站集),寫好部署操作手冊。完工。