diff --git a/db/standard/zentao20.0.sql b/db/standard/zentao20.0.beta2.sql similarity index 100% rename from db/standard/zentao20.0.sql rename to db/standard/zentao20.0.beta2.sql diff --git a/db/update18.10.1.sql b/db/update18.10.1.sql index b508d1ee67..c69ff97925 100644 --- a/db/update18.10.1.sql +++ b/db/update18.10.1.sql @@ -1,6 +1,3 @@ -ALTER TABLE `zt_demand` ADD COLUMN `feedback` mediumint(9) NOT NULL DEFAULT '0'; -ALTER TABLE `zt_demand` ADD COLUMN `keywords` varchar(255) NOT NULL DEFAULT ''; - CREATE TABLE IF NOT EXISTS `zt_ai_model` ( `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT, `type` varchar(20) NOT NULL, diff --git a/db/update18.10.sql b/db/update18.10.sql index 8e4858591b..81e42431a7 100644 --- a/db/update18.10.sql +++ b/db/update18.10.sql @@ -1,5 +1,7 @@ ALTER TABLE `zt_demandpool` ADD COLUMN `products` varchar(255) NOT NULL DEFAULT ''; ALTER TABLE `zt_project` ADD COLUMN `parallel` mediumint(9) NOT NULL DEFAULT '0'; +ALTER TABLE `zt_demand` ADD COLUMN `feedback` mediumint(9) NOT NULL DEFAULT '0'; +ALTER TABLE `zt_demand` ADD COLUMN `keywords` varchar(255) NOT NULL DEFAULT ''; ALTER TABLE `zt_demand` CHANGE `product` `product` varchar(255) NOT NULL DEFAULT ''; ALTER TABLE `zt_story` ADD COLUMN `retractedReason` ENUM('', 'omit', 'other') NOT NULL DEFAULT ''; ALTER TABLE `zt_story` ADD COLUMN `retractedBy` varchar(30) NOT NULL DEFAULT ''; @@ -34,4 +36,3 @@ UPDATE `zt_metric` SET `definition` = '所有的任务的预计工时数求和\r UPDATE `zt_metric` SET `definition` = '所有的任务的消耗工时数求和\r\n过滤父任务\r\n过滤已删除的任务\r\n过滤已删除项目的任务\r\n过滤已删除执行的任务' WHERE `code` = 'consume_of_task'; UPDATE `zt_metric` SET `definition` = '所有的任务的剩余工时数求和\r\n过滤父任务\r\n过滤已删除的任务\r\n过滤已删除项目的任务\r\n过滤已删除执行的任务' WHERE `code` = 'left_of_task'; UPDATE `zt_metric` SET `definition` = '所有的任务个数求和\r\n完成时间为某日\r\n过滤已删除的任务\r\n过滤已删除项目的任务\r\n过滤已删除执行的任务' WHERE `code` = 'count_of_daily_finished_task'; - diff --git a/doc/CHANGELOG b/doc/CHANGELOG index 5608531451..1103c96f03 100644 --- a/doc/CHANGELOG +++ b/doc/CHANGELOG @@ -1,4 +1,230 @@ 2024-02-29 20.0.beta2 +修复的Bug +开源版: +41532 【20版本】显示文档设置弹窗页面显示滚动条 +44531 【20版本】地盘仪表盘使用帮助区块中个性化设置的交互问题 +44607 【20版本】仪表盘添加区块的字段顺序错误 +45001 【20版本】看板迭代的按计划关联与需求规范不同 +45092 【20改版】地盘日程批量添加待办页面样式及交互逻辑与需求不符 +45859 【20版本】Bug严重程度和优先级自定义后显示颜色不正确 +45910 【20版本】调用单点登录接口报错 +46141 18.6升级至biz8.11项目下代码库报错 +46144 【20版本】项目关联研发需求页面,子需求前没有“子”标签 +46151 【20版本】复制执行,页面滚动有问题 +46207 【20版本】用户登录强制修改密码且校验失败 +46210 【20版本】登录后显示空白页 +46236 【20版本】后台二级导航不显示了 +46245 【20版本】忘记密码后重置报错 +46250 【20版本】创建日志弹窗中显示重复内容 +46293 【20版本】登录页面切换语言项按钮显示不正确且无法点击 +46305 【20版本】需求评审报错 +46307 【20版本】Bug详情页编辑备注,图片丢失 +46312 【20版本】执行下任务详情页缺少“相关用例”的展示 +46314 【20版本】创建项目时选择多分支产品后有js报错 +46323 【20版本】批量创建需求报错 +46349 【20版本】安装请选择使用模式页没加上AI +46350 【20版本】pms12.3删除表,升级pms20.0一致性检查时未显示添加表的sql +46351 【20版本】点击Bug二级导航有报错 +46352 【20版本】12.4升级到20.0时出现报错 +46353 【20版本】新手教程右侧导航显示样式不正确 +46354 【20版本】仪表盘每个区块都有一样的报错信息 +46356 【20版本】批量添加用户,输入密码后,点击保存仍然校验:密码不能为空 +46357 【20版本】编辑子项目集,父项目集字段只读无法修改 +46359 tar.xz源码包安装没有设置默认权限 +46365 【20版本】修改档案会清空用户权限分组 +46368 【20版本】所有的自定义列设置弹窗中底部都显示了两个保存按钮 +46370 【20版本】文档更新信息和操作按钮重叠显示了 +46371 【20版本】按模块分配权限弹窗有两个重复标题 +46372 【20版本】升级前将字段属性改大,升级后字段属性恢复到了原本的值 +46375 【20版本】接口空间的左侧树形导航中,接口库名称可显示区域过窄 +46376 【20版本】产品列表页执行搜索,搜索结果显示不正确 +46378 【20版本】用例列表的用例类型下拉列表无法展开 +46379 【20版本】升级后项目关联研发需求页面搜索不到相关的数据 +46381 【20版本】执行用例窗口点击转Bug后未跳转到提Bug页面 +46382 【20版本】编辑器保存模板点击无响应 +46383 【20版本】编辑器右上角边框线样式不正确 +46385 【20版本】任务树状图中未指派时显示空圈 +46386 【20版本】创建版本页面后台设置的必填项时为空提示语言项为英文 +46387 【20版本】任务列表勾选任务后下方统计信息部分遮挡 +46388 【20版本】自定义列页面显示2个保存按钮 +46390 【20版本】瀑布阶段列表中新建阶段保存后弹窗没有关闭 +46391 【20版本】影响版本勾选所有没列出项目和执行下的版本 +46392 【20版本】提Bug控制台有报错 +46393 【20版本】产品计划的看板视图页面样式异常 +46394 【20版本】附件添加成功详情页不显示 +46398 【20版本】AI下我的收藏标签应固定显示 +46400 【20版本】用例库中导出模板的用例类型字段不是单选下拉格式 +46402 【20版本】执行文档附件库列表中附件名称未上下居中 +46404 【20版本】升级后后台公开课显示空 +46405 【20版本】执行团队列表下方总计处工时数据错误 +46406 【20版本】截止日期字段缺失延期样式 +46407 【20版本】升级后地盘默认区块缺失 +46408 【20版本】多分支产品研发需求列表自定义设置中没有显示平台/分支字段 +46409 【20版本】报表由饼图切换为柱状图折线图显示不正确 +46410 【20版本】添加执行页面显示“迭代”概念 +46411 【20版本】自定义列恢复默认没生效 +46412 【20版本】执行下二级导航DevOps点击后显示为代码库 +46414 【20版本】项目仪表盘中项目总览区块的投入的详细信息,在小屏幕上鼠标悬停显示不全 +46417 【20版本】Bug无所属项目/执行时,字段信息显示为0 +46418 【20版本】没有激活次数时,列表显示为0 +46424 【20版本】计划完成时间为长期时,导出项目csv的计划完成时间有误 +46427 【20版本】项目集1.5级导航,点击三角角标,导航会收起 +46434 【20版本】创建代码库页面有代码报错 +46439 【20版本】执行下Bug列表所属执行,所属计划等字段显示为ID +46442 【20版本】瀑布项目设计详情页相关设计处链接错误 +46447 【20版本】单元测试用例导出显示了选中记录 +46448 【20版本】待办导出后的文件名称显示不对 +46451 【20版本】执行研发需求相关Bug和相关用例列表中字段排序按钮不可用 +46452 【20版本】执行下测试单关联用例页面执行时间显示0000-00-00 00:00:00 +46453 【20版本】执行概况页面关联计划名称显示不全 +46455 【20版本】通用看板分页设置没有被保存 +46456 【20版本】升级前将zt_doc表的type字段值改大,升级后恢复了原来的值 +46458 【20版本】运营管理界面项目团队下角色不显示 +46460 【20版本】xmind导入页返回到其他应用页面 +46461 【20版本】导入成功和暂时没有数据同时提示 +46463 【20版本】关联用例页面,字段排序不生效 +46468 【20版本】创建待办后指派给显示了当前用户 +46469 【20版本】批量建指派给他人的待办后[指派他人]tab下无数据 +46471 【20版本】待办详情页开始完成等状态变化按钮点击没有反应 +46472 【20版本】用例的软需变更列表状态没有提示样式 +46473 【20版本】从用例库导入用例,有报错 +46474 【20版本】产品发布发送通知时代码报错 +46476 【20版本】批量编辑计划控制台报错 +46478 【20版本】添加区块弹窗中鼠标悬浮时内容会隐藏 +46479 【20版本】任务记日志弹窗中编辑工时按钮置灰后仍然可以点 +46481 【20版本】组织下点击执行返回不正确 +46482 【20版本】点击模块名,页面有代码报错 +46486 【20版本】项目按计划关联研发需求可以选到当前项目未关联的产品分支下的计划 +46487 【20版本】从用例库导入用例列表,所属模块显示错误 +46492 【20版本】场景的优先级和所属场景显示不正确 +46493 【20版本】复制看板没有复制其他看板的列和泳道 +46498 【20版本】模块和场景数据没联动 +46501 【20版本】目标的功能配置缺少内容 +46504 【20版本】选择模块后不显示场景了 +46508 【20版本】搜索用例有代码报错 +46510 【20版本】应用都全局没生效 +46511 【20版本】文档的搜索功能失效 +46513 【20版本】应用到全局没生效 +46515 【20版本】运营管理界面项目下删除看板,跳转页面错误 +46516 【20版本】打印任务看板,内容有遮挡 +46517 【20版本】设置显示模块,列表实际没显示模块 +46518 【20版本】运营管理界面项目下看板中不应显示看板下拉和分组下拉 +46520 【20版本】建议调整文档的左侧树形导航收起样式 +46521 【20版本】运营管理界面项目看板下不显示项目和看板 +46522 【20版本】报表的饼图/柱状图/折线图显示空白 +46523 【20版本】运营管理界面项目列表操作列按钮不正确 +46524 【20版本】测试用例导入xmind提示上传失败 +46526 【20版本】项目白名单部门字段应居左显示 +46527 【20版本】12.4升级到20.0手工归并数据页面提示文案和选项不对应 +46528 【20版本】创建非自定义类型待办后没有刷新页面 +46529 【20版本】执行分组视图下点击导出有js报错 +46531 【20版本】执行列表点击搜索后页面显示错误 +46532 【20版本】产品视角下,点击产品列表里的勾选框,控制台有报错 +46534 【20版本】产品视角下,点击搜索按钮,没有弹出搜索条件 +46536 【20版本】创建执行节点快照缺少语言项 +46537 【20版本】执行节点显示密码的逻辑不正确 +46538 【20版本】无法连接执行节点 +46540 【20版本】创建快照,快照名称没有必填项标识 +46546 【20版本】导出阶段弹窗初始化,文件名称默认为【未完成执行】 +46548 【20版本】用例的模块设置下切换1.5级导航的分支,返回了用例列表 +46549 【20版本】选择模块点击API右侧显示混乱 +46551 【20版本】用例的模块设置下编辑模块,叉掉分支后保存502 +46552 【20版本】测试用例库的功能配置页面字段显示不全 +46554 【20版本】后台配置语言模型后测试链接有代码提示 +46555 【20版本】添加语言模型成功后点击返回进入添加语言模型页面 +46557 【20版本】升级前关闭用户需求功能升级后再打开,非超级管理员产品下看不到用户需求功能 +46558 【20版本】批量编辑执行页面负责人下拉按钮和同上重叠 +46559 【20版本】文档下搜索的右侧保存搜索条件无法正常收起 +46564 【20版本】点开手册后,刷新页面报错 +46565 【20版本】配置语言模型后提词不显示再相应的页面 +46567 【20版本】创建场景页面,维护模块没刷新该字段 +46568 【20版本】转入任务列表中显示“迭代”概念 +46570 【20版本】关闭的执行禁止修改关闭执行后点击执行视图有报错 +46574 【20版本】瀑布项目下,版本关联研发需求报错 +46576 【20版本】项目列表项目链接不正确 +46578 【20版本】批量添加用户初始化,页面字段与自定义表单不符 +46579 【20版本】点击人员弹窗中的邮箱,跳转到了地盘 +46580 【20版本】批量编辑长期项目,批量编辑页面的计划完成时间和可用工作日只读无法修改 +46585 【20版本】自定义列显示所属产品和所属模块 +46588 【20版本】测试单导出测试用例页面不正确 +46592 【20版本】指派给我的Bug列表底部显示滚动条 +46594 【20版本】修改研发/用户需求语言项保存后页面没有显示保存的内容 +46595 【20版本】文档仪表盘收藏排行榜无数据时未显示提示文案 +46597 【20版本】组织团队下任务列表多人任务名称前显示了“子”标识 +46598 【20版本】组织团队下任务详情页点击模块名称后页面内容和导航不一致 +46600 【20版本】全局创建Bug页面选择项目和所属执行后影响版本下拉只列出了主干 +46602 【20版本】提研发需求页面新建计划后下拉控件样式不对 +46605 【20版本】在组织团队下需求详情页点击相关版本/发布处的版本名称后导航和内容不对应 +46607 【20版本】创建项目集计划完成日期默认显示今日 +46613 【20版本】创建项目集页面选择父项目集后无法切换其他父项目集 +46615 【20版本】12版本升级到轻量模式产品下创建项目选不到关联产品下的计划 +46618 【20版本】个人档案中姓名显示在了头像下方 +46627 【20版本】合并请求关联Bug页面搜索后有报错 +46629 【20版本】组织团队下任务详情页点击相关合并请求/相关版本名称后页面内容和导航不对应 +46634 【20版本】创建/编辑执行页面保存时提示计划开始和计划完成应为合法日期 +46636 【20版本】点击日程页面中间的添加待办按钮,显示添加待办页面,创建后没有跳转 +46640 【20版本】运营管理界面项目下看板中操作按钮不正确 +46644 【20版本】安装禅道的设置管理员步骤中,英文语言项显示有遮挡 +46646 【20版本】编辑用例模块时将普通产品切换至多分支产品,未显示分支的下拉框 +46662 【20版本】执行下创建测试报告页面点击返回,跳转到了测试模块的测试报告下 +46666 【20版本】计划列表中点击激活无反应 +46667 【20版本】在产品计划的研发需求列表提研发需求,在提需求页面点击返回无效 +46668 【20版本】20.0.beta升级到20.0,AI模块点击报错 +46670 【20版本】升级至20版本没有显示界面切换提示 +46673 【20版本】合并请求关联任务页面搜索后有报错 +46674 【20版本】合并请求页面关联需求/Bug/任务页面搜索后再刷新返回按钮和标签不显示了 +46679 【20版本】18.6升级到18.7再升级到20提词会重复添加 +46705 【20版本】创建项目集勾选长期,提示计划完成不能为空 +46715 【20版本】20.0beta1升级到20.0beta2后,内置ai提词、小程序没有了 +46718 【20版本】宿主机里添加执行,获取不到镜像 +46721 【20版本】套件关联用例页面返回按钮样式不正确 +46723 【20版本】导入xmind数据弹窗提示没显示 +46732 【20版本】多分支产品批量提Bug页面,自定义表单项中未勾选分支,页面默认显示分支字段 +46742 【20版本】项目列表的需求规模显示成了“NaN” +46747 【20版本】项目仪表盘目标显示为需求 +46760 【20版本】看板设置导入功能显示逻辑不正确 +46782 【20版本】删除代码库后,地盘动态下,显示的是代码库编号 +46833 【20版本】有产品项目用户需求下显示了”关联研发需求“ +46839 【20版本】有产品项目的设置下3级导航没有产品 +46866 【20版本】有产品项目下用户需求细分报错 +46874 【20版本】多人任务的一生中,“最后编辑”内容弄显示不全 +46879 【20版本】编辑完成多人任务时生成的日志,保存时提示“预计剩余不能为0” +46898 【20版本】项目用户需求列表页关联的研发需求查看详情报错 +46899 【20版本】运营管理界面项目下有报错 +46904 【20版本】项目研发需求列表关联研发需求代码报错 +46906 【20版本】Bug转任务时所属执行可以选择与所选项目无关的执行 +46907 【20版本】无执行项目任务列表转入Bug页搜索表单下拉显示了“所属执行” +46909 【20版本】复制一个无执行无产品项目保存后成了有执行的项目 +46910 【20版本】删除文档库目录后,恢复提示应修改 +46911 【20版本】通用看板还原父列有报错 +46912 【20版本】测试单的用例列表页执行搜索时代码报错 +46914 【20版本】运营管理界面目标列表页下方统计信息显示错乱 +46915 【20版本】执行研发需求报表页面1.5级导航不显示了 +46917 【20版本】运营管理界面的项目仪表盘中不应该显示[待测测试单列表]区块 +46923 【20版本】目标列表tab筛选无效且没有选中样式 +46925 【20版本】系统内只有看板类型迭代时,进入看板执行代码报错 +46926 【20版本】无产品无迭代项目,bug可以指派给【空】 +46928 【20版本】选择不显示在制品数量无法创建看板 +46930 【20版本】项目下添加干系人点击保存没有反应 +46931 【20版本】运营管理界面任务详情中没有显示相关目标 +46934 【20版本】游客登录后用例列表有报错 +46935 【20版本】仪表盘未完成的项目统计区块点击瀑布项目名称后有报错 +46936 【20版本】瀑布项目下点击二级导航的用户需求后报错 +46937 【20版本】有产品无迭代项目二级导航没有显示用户需求 +46939 【20版本】计划创建执行成功后提示文案显示“迭代” +46942 【20版本】批量编辑执行页面显示了“迭代”概念 +46943 【20版本】创建测试单页面创建版本时弹窗中所属产品显示不一致 +46950 【20版本】编辑父任务后开始/工时/完成等操作按钮可以操作了 +46959 【20版本】Windows一键安装包的RR服务启动失败 +46964 【20版本】待处理任务列表父任务开始/完成/记录工时按钮未置灰 +46965 【20版本】任务列表导入Bug时Bug详情页操作栏部分遮挡 +46966 【20版本】后台模式管理页与一级导航顺序不一致 +46969 【20版本】任务看板建任务页面点击关联研发需求后搜索表单未显示完整 +46971 【20版本】私有项目型项目编辑页面添加项目白名单后不显示在白名单列表中 +46985 【20版本】复制无执行项目和编辑无执行项目页面显示了“长期”复选框 +46986 【20版本】有所属项目集的无执行项目任务列表未显示转入任务按钮 +46990 【20版本】批量编辑执行页面自定义表单勾选“团队名称”保存后未显示该字段 +46996 【20版本】任务详情页点击确认需求变更按钮,按钮没消失 2024-02-20 18.11 完成的需求 diff --git a/extension/lite/block/ext/config/lite.php b/extension/lite/block/ext/config/lite.php new file mode 100644 index 0000000000..3e73f52a81 --- /dev/null +++ b/extension/lite/block/ext/config/lite.php @@ -0,0 +1,5 @@ +block->projectstatistic->items['story'] = array(); +$config->block->projectstatistic->items['story'][] = array('field' => 'storyPoints', 'unit' => 'unit'); +$config->block->projectstatistic->items['story'][] = array('field' => 'done', 'unit' => 'unit'); +$config->block->projectstatistic->items['story'][] = array('field' => 'undone', 'unit' => 'unit'); diff --git a/extension/lite/block/ext/lang/zh-cn/lite.php b/extension/lite/block/ext/lang/zh-cn/lite.php index 2c5ee018ad..42d9eb4275 100644 --- a/extension/lite/block/ext/lang/zh-cn/lite.php +++ b/extension/lite/block/ext/lang/zh-cn/lite.php @@ -18,14 +18,15 @@ $lang->block->finishedTasks = '完成的任务数'; $lang->block->story = '目标'; $lang->block->storyCount = '目标数'; +$lang->block->projectstatistic->storyPoints = '总目标'; $lang->block->default['full']['my'][] = array('title' => '看板列表', 'module' => 'execution', 'code' => 'scrumlist', 'width' => '2', 'height' => '6', 'left' => '0', 'top' => '45', 'params' => array('type' => 'doing', 'orderBy' => 'id_desc', 'count' => '15')); -$lang->block->modules['kanban'] = new stdclass(); -$lang->block->modules['kanban']->availableBlocks['scrumoverview'] = "{$lang->projectCommon}概况"; -$lang->block->modules['kanban']->availableBlocks['scrumlist'] = $lang->executionCommon . '列表'; -$lang->block->modules['kanban']->availableBlocks['sprint'] = $lang->executionCommon . '总览'; -$lang->block->modules['kanban']->availableBlocks['projectdynamic'] = '最新动态'; +$lang->block->modules['kanbanproject'] = new stdclass(); +$lang->block->modules['kanbanproject']->availableBlocks['scrumoverview'] = "{$lang->projectCommon}概况"; +$lang->block->modules['kanbanproject']->availableBlocks['scrumlist'] = $lang->executionCommon . '列表'; +$lang->block->modules['kanbanproject']->availableBlocks['sprint'] = $lang->executionCommon . '总览'; +$lang->block->modules['kanbanproject']->availableBlocks['projectdynamic'] = '最新动态'; $lang->block->modules['project'] = new stdclass(); $lang->block->modules['project']->availableBlocks['project'] = "{$lang->projectCommon}列表"; diff --git a/extension/lite/product/ext/control/browse.php b/extension/lite/product/ext/control/browse.php index d22f4a9d88..e753d49573 100644 --- a/extension/lite/product/ext/control/browse.php +++ b/extension/lite/product/ext/control/browse.php @@ -81,7 +81,6 @@ class product extends control $project = $projectID ? $this->loadModel('project')->getByID($projectID) : null; /* Get stories. */ - if(in_array($browseType, array('reviewing', 'draft', 'changing'))) $browseType .= 'story'; $stories = $this->productZen->getStories($projectID, $productID, $branchID, $moduleID, $param, $storyType, $browseType, $orderBy, $pager); /* Process the sql, get the conditon partion, save it to session. */ diff --git a/extension/lite/product/ext/lang/zh-cn/lite.php b/extension/lite/product/ext/lang/zh-cn/lite.php index e0e2eb20d7..cfe218524c 100644 --- a/extension/lite/product/ext/lang/zh-cn/lite.php +++ b/extension/lite/product/ext/lang/zh-cn/lite.php @@ -1,3 +1,4 @@ product->noModule = '您现在还没有目录信息'; -$lang->product->storySummary = "本页共 %s 个%s,预计 %s 个{$lang->hourCommon}。"; +$lang->product->noModule = '您现在还没有目录信息'; +$lang->product->storySummary = "本页共 %s 个{$lang->SRCommon},预计 %s 个{$lang->hourCommon}。"; +$lang->product->checkedSRSummary = "选中 %total% 个{$lang->SRCommon},预计 %estimate% 个{$lang->hourCommon}。"; diff --git a/extension/lite/project/ext/css/kanban/lite.ui.css b/extension/lite/project/ext/css/kanban/lite.ui.css index e69de29bb2..93c81b9dbf 100644 --- a/extension/lite/project/ext/css/kanban/lite.ui.css +++ b/extension/lite/project/ext/css/kanban/lite.ui.css @@ -0,0 +1,2 @@ +.kanban-lane-name {display: none !important;} +.kanban-header-lane-name {display: none !important;} diff --git a/extension/lite/projectstory/ext/lang/de/lite.php b/extension/lite/projectstory/ext/lang/de/lite.php index 89163e28fe..5f60b3fe6b 100644 --- a/extension/lite/projectstory/ext/lang/de/lite.php +++ b/extension/lite/projectstory/ext/lang/de/lite.php @@ -2,5 +2,3 @@ unset($lang->projectstory->featureBar['story']['changed']); unset($lang->projectstory->featureBar['story']['linkedExecution']); unset($lang->projectstory->featureBar['story']['unlinkedExecution']); -$lang->projectstory->featureBar['story']['closedstory'] = $lang->projectstory->featureBar['story']['closed']; -unset($lang->projectstory->featureBar['story']['closed']); diff --git a/extension/lite/projectstory/ext/lang/en/lite.php b/extension/lite/projectstory/ext/lang/en/lite.php index 89163e28fe..5f60b3fe6b 100644 --- a/extension/lite/projectstory/ext/lang/en/lite.php +++ b/extension/lite/projectstory/ext/lang/en/lite.php @@ -2,5 +2,3 @@ unset($lang->projectstory->featureBar['story']['changed']); unset($lang->projectstory->featureBar['story']['linkedExecution']); unset($lang->projectstory->featureBar['story']['unlinkedExecution']); -$lang->projectstory->featureBar['story']['closedstory'] = $lang->projectstory->featureBar['story']['closed']; -unset($lang->projectstory->featureBar['story']['closed']); diff --git a/extension/lite/projectstory/ext/lang/fr/lite.php b/extension/lite/projectstory/ext/lang/fr/lite.php index 89163e28fe..5f60b3fe6b 100644 --- a/extension/lite/projectstory/ext/lang/fr/lite.php +++ b/extension/lite/projectstory/ext/lang/fr/lite.php @@ -2,5 +2,3 @@ unset($lang->projectstory->featureBar['story']['changed']); unset($lang->projectstory->featureBar['story']['linkedExecution']); unset($lang->projectstory->featureBar['story']['unlinkedExecution']); -$lang->projectstory->featureBar['story']['closedstory'] = $lang->projectstory->featureBar['story']['closed']; -unset($lang->projectstory->featureBar['story']['closed']); diff --git a/extension/lite/projectstory/ext/lang/zh-cn/lite.php b/extension/lite/projectstory/ext/lang/zh-cn/lite.php index 89163e28fe..5f60b3fe6b 100644 --- a/extension/lite/projectstory/ext/lang/zh-cn/lite.php +++ b/extension/lite/projectstory/ext/lang/zh-cn/lite.php @@ -2,5 +2,3 @@ unset($lang->projectstory->featureBar['story']['changed']); unset($lang->projectstory->featureBar['story']['linkedExecution']); unset($lang->projectstory->featureBar['story']['unlinkedExecution']); -$lang->projectstory->featureBar['story']['closedstory'] = $lang->projectstory->featureBar['story']['closed']; -unset($lang->projectstory->featureBar['story']['closed']); diff --git a/extension/lite/story/ext/model/lite.php b/extension/lite/story/ext/model/lite.php index c07bf17bbd..33d472e73c 100644 --- a/extension/lite/story/ext/model/lite.php +++ b/extension/lite/story/ext/model/lite.php @@ -1,5 +1,5 @@ config->vision == 'lite') { diff --git a/framework/router.class.php b/framework/router.class.php index c83a902d09..ca90bddbec 100755 --- a/framework/router.class.php +++ b/framework/router.class.php @@ -344,16 +344,16 @@ class router extends baseRouter /* Set default story concept and init UR and SR concept. */ $lang->URCommon = $URPairs[$config->URSR] ?? reset($URPairs); $lang->SRCommon = $SRPairs[$config->URSR] ?? reset($SRPairs); - } - /* Replace common lang. */ - $customMenus = array(); - try - { - $customMenus = $this->dbQuery('SELECT * FROM' . TABLE_LANG . "WHERE `module`='common' AND `lang`='{$this->clientLang}' AND `section`='' AND `vision`='{$config->vision}'")->fetchAll(); + /* Replace common lang. */ + $customMenus = array(); + try + { + $customMenus = $this->dbQuery('SELECT * FROM' . TABLE_LANG . "WHERE `module`='common' AND `lang`='{$this->clientLang}' AND `section`='' AND `vision`='{$config->vision}'")->fetchAll(); + } + catch(PDOException){} + foreach($customMenus as $menu) if(isset($lang->{$menu->key})) $lang->{$menu->key} = $menu->value; } - catch(PDOException){} - foreach($customMenus as $menu) if(isset($lang->{$menu->key})) $lang->{$menu->key} = $menu->value; } } diff --git a/lib/xmind/xmind.class.php b/lib/xmind/xmind.class.php index 73ec702280..04c94ce99b 100644 --- a/lib/xmind/xmind.class.php +++ b/lib/xmind/xmind.class.php @@ -125,12 +125,21 @@ class xmind { if(empty($case->testcaseID)) continue; - if(isset($sceneNodes[$case->sceneID])) $parentNode = $sceneNodes[$case->sceneID]; - - if(!isset($parentNode)) $parentNode = $moduleNodes[$case->moduleID]; - if(!isset($parentNode)) $parentNode = $productNode; - - $this->createOneTestcaseNode($case, $xmlDoc, $context, $parentNode); + if($case->sceneID && isset($sceneNodes[$case->sceneID])) + { + $this->createOneTestcaseNode($case, $xmlDoc, $context, $sceneNodes[$case->sceneID]); + } + else + { + if($case->moduleID && isset($moduleNodes[$case->moduleID])) + { + $this->createOneTestcaseNode($case, $xmlDoc, $context, $moduleNodes[$case->moduleID]); + } + else + { + $this->createOneTestcaseNode($case, $xmlDoc, $context, $productNode); + } + } } } diff --git a/lib/zin/core/js.class.php b/lib/zin/core/js.class.php index 2aa1ae20b8..c8f4dd3b36 100644 --- a/lib/zin/core/js.class.php +++ b/lib/zin/core/js.class.php @@ -384,9 +384,9 @@ class js implements \JsonSerializable, iDirective * 序列化为 JSON。 * * @access public - * @return string + * @return mixed */ - public function jsonSerialize(): string + public function jsonSerialize(): mixed { $js = trim($this->toJS()); if(str_ends_with(';', $js)) $js = substr($js, 0, -1); diff --git a/lib/zin/core/node.class.php b/lib/zin/core/node.class.php index 547466d7f2..898db140cd 100644 --- a/lib/zin/core/node.class.php +++ b/lib/zin/core/node.class.php @@ -570,9 +570,9 @@ class node implements \JsonSerializable * Serialized to JSON string. * * @access public - * @return string + * @return mixed */ - public function jsonSerialize(): string + public function jsonSerialize(): mixed { return json_encode($this->toJSON()); } diff --git a/lib/zin/wg/backbtn/v1.php b/lib/zin/wg/backbtn/v1.php index 1209e309cc..16f5705336 100644 --- a/lib/zin/wg/backbtn/v1.php +++ b/lib/zin/wg/backbtn/v1.php @@ -43,10 +43,10 @@ class backBtn extends btn global $app; $backs = array( - 'task' => 'execution-task,my-work,my-contribute,execution-tree,execution-grouptask,project-execution,product-track,repo-view', + 'task' => 'execution-task,my-work,my-contribute,execution-tree,execution-grouptask,project-execution,product-track,repo-view,story-change', 'story' => 'product-browse,projectstory-story,execution-story,my-work,my-contribute,productplan-view,build-view,projectbuild-view,product-track,repo-view', - 'bug' => 'bug-browse,project-bug,my-work,my-contribute,execution-bug,bug-view,qa-index,execution-task,product-track,execution-task,task-view,repo-view', - 'testcase' => 'testcase-browse,project-testcase,my-work,my-contribute,execution-testcase,testtask-cases,testsuite-view,product-browse,testcase-view,qa-index,caselib-browse,product-track', + 'bug' => 'bug-browse,project-bug,my-work,my-contribute,execution-bug,bug-view,qa-index,execution-task,product-track,execution-task,task-view,repo-view,story-change', + 'testcase' => 'testcase-browse,project-testcase,my-work,my-contribute,execution-testcase,testtask-cases,testsuite-view,product-browse,testcase-view,qa-index,caselib-browse,product-track,story-change', 'testsuite' => 'testsuite-browse,testsuite-view,', 'testtask' => 'testtask-browse,testtask-cases,qa-index,testcase-browse,', 'testreport' => 'testreport-browse,project-testreport,execution-testreport,execution-testtask', diff --git a/lib/zin/wg/batchactions/js/v1.js b/lib/zin/wg/batchactions/js/v1.js index 008846036d..055c6676b1 100644 --- a/lib/zin/wg/batchactions/js/v1.js +++ b/lib/zin/wg/batchactions/js/v1.js @@ -15,7 +15,7 @@ function isNumeric(obj) * @access public * @return void */ -function addItem(e) +window.addItem = function(e) { const obj = e.target const thisRow = $(obj).closest('.form-row'); diff --git a/lib/zin/wg/docmenu/css/v1.css b/lib/zin/wg/docmenu/css/v1.css index 197a039177..65ce0a02c6 100644 --- a/lib/zin/wg/docmenu/css/v1.css +++ b/lib/zin/wg/docmenu/css/v1.css @@ -35,3 +35,5 @@ .has-module-menu-header #mainMenu {padding-left: 215px;} .has-module-menu-header.is-sidebar-left-collapsed #mainMenu {padding-left: 0;} #mainContent > .sidebar:not(.is-collapsed) {min-width: 200px !important;} + +.doc-setting-modal > .modal-dialog > .modal-content > .modal-body {padding: 0 2rem;} diff --git a/lib/zin/wg/docmenu/v1.php b/lib/zin/wg/docmenu/v1.php index 1240d3f394..a36e8ebce1 100644 --- a/lib/zin/wg/docmenu/v1.php +++ b/lib/zin/wg/docmenu/v1.php @@ -395,33 +395,27 @@ class docMenu extends wg global $app; $lang = $app->loadLang('datatable')->datatable; - $currentModule = $app->rawModule; - $currentMethod = $app->rawMethod; - if(!$settingText) $settingText = $lang->moduleSetting; - $datatableId = $app->moduleName . ucfirst($app->methodName); - return div ( setClass('col gap-2 py-3 px-7'), - $settingLink - ? a + $settingLink ? a + ( + setClass('btn'), + setStyle('background', 'rgb(var(--color-primary-50-rgb))'), + setStyle('box-shadow', 'none'), + set('data-app', $app->tab), + set('data-size', array('width' => '600px', 'height' => '200px')), + set('data-toggle', 'modal'), + set('data-class-name', 'doc-setting-modal'), + set::href($settingLink), + span ( - setClass('btn'), - setStyle('background', 'rgb(var(--color-primary-50-rgb))'), - setStyle('box-shadow', 'none'), - set('data-app', $app->tab), - set('data-size', array('width' => '600px', 'height' => '200px')), - set('data-toggle', 'modal'), - set::href($settingLink), - span - ( - setClass('text-primary'), - $settingText - ) + setClass('text-primary'), + $settingText ) - : null + ) : null ); } diff --git a/lib/zin/wg/dropmenu/v1.php b/lib/zin/wg/dropmenu/v1.php index 12dd9d8523..b04d2373dc 100644 --- a/lib/zin/wg/dropmenu/v1.php +++ b/lib/zin/wg/dropmenu/v1.php @@ -111,7 +111,7 @@ class dropmenu extends wg $branchName = $app->control->branch->getByID((string)$branchID); } if($module == 'testtask' && $method == 'browse') $extra = data('type'); - if(($app->moduleName == 'tree' && $app->methodName == 'browse') || ($module == 'tree' || $method == 'browse')) + if($app->moduleName == 'tree' && $app->methodName == 'browse') { $module = 'tree'; $method = 'browse'; @@ -162,6 +162,11 @@ class dropmenu extends wg { $object = $app->control->loadModel($tab)->getByID((int)$objectID); $text = $object ? $object->name : ''; + if($tab == 'execution' && !$object->hasProduct) + { + $project = $app->control->loadModel('project')->getByID((int)$object->project); + $text = $project->name . ' / ' . $text; + } } /* 如果是产品的 1.5 级导航,把当前产品的类型存入 session 以供后面使用。*/ diff --git a/lib/zin/wg/severitylabel/v1.php b/lib/zin/wg/severitylabel/v1.php index f34bccd5a8..985310b30c 100644 --- a/lib/zin/wg/severitylabel/v1.php +++ b/lib/zin/wg/severitylabel/v1.php @@ -7,7 +7,7 @@ class severityLabel extends wg protected static array $defineProps = array( 'level: string|int', // 严重程度等级。 'text?: string|array', // 标签文本或标签文本映射对象,如果不指定从 $lang->$moduleName->severityList 中获取。 - 'isIcon?: bool=false' // 是否显示为图标。 + 'isIcon?: bool' // 是否显示为图标。 ); protected function onAddChild(mixed $child) diff --git a/module/action/control.php b/module/action/control.php index 978387bb90..e71c4c989f 100755 --- a/module/action/control.php +++ b/module/action/control.php @@ -171,11 +171,25 @@ class action extends control if($oldAction->objectType == 'module' && $confirmChange == 'no') { $module = $this->loadModel('tree')->getById($oldAction->objectID); - $repeatName = $this->loadModel('tree')->checkUnique($module); - if($module->type == 'doc' && $module->parent > 0 && !$repeatName) + $repeatName = $this->tree->checkUnique($module); + if($module->type == 'doc' && $module->parent > 0 && !$repeatName) { - $url = $this->createLink('action', 'undelete', "action={$actionID}&browseType={$browseType}&confirmChange=yes"); - return $this->send(array('result' => 'fail', 'callback' => "zui.Modal.alert({message: '{$this->lang->action->undeleteModuleTip}'}); $.ajaxSubmit({url: '{$url}'});")); + $parents = $this->tree->getParents($oldAction->objectID, true); + $isDeletedParent = false; + foreach($parents as $parent) + { + if($parent->id == $oldAction->objectID) continue; + if($parent->deleted) + { + $isDeletedParent = true; + break; + } + } + if($isDeletedParent) + { + $url = $this->createLink('action', 'undelete', "action={$actionID}&browseType={$browseType}&confirmChange=yes"); + return $this->send(array('result' => 'fail', 'callback' => "zui.Modal.confirm({message: '{$this->lang->action->undeleteModuleTip}', icon: 'icon-exclamation-sign', iconClass: 'warning-pale rounded-full icon-2x'}).then((res) => {if(res) $.ajaxSubmit({url: '{$url}'});});")); + } } } elseif($oldAction->objectType == 'task' && $confirmChange == 'no') diff --git a/module/action/lang/de.php b/module/action/lang/de.php index 1d6518a343..dbc78df586 100644 --- a/module/action/lang/de.php +++ b/module/action/lang/de.php @@ -68,7 +68,7 @@ $lang->action->hasCreatedTask = 'Tasks have been created in this parent phas $lang->action->hasDeletedParent = 'Restoring this phase requires restoring the deleted parent phase %s at the same time.'; $lang->action->hasChangedAttr = "After recovery, the phase type will be adjusted to '%s' according to the parent phase type."; $lang->action->whetherToRestore = 'Confirm to restore?'; -$lang->action->undeleteModuleTip = 'Once the subdirectory has been restored, its parent directory will also be automatically recovered and synchronized.'; +$lang->action->undeleteModuleTip = 'Once the subdirectory has been restored, its parent directory will also be automatically recovered and synchronized, would you like to proceed with restoring the directory?'; $lang->action->undeleteTaskTip = 'The execution associated with this task has been removed. If you choose to restore it, the task will no longer be visible. Would you like to proceed with restoring the task?'; $lang->action->hasOtherType = array(); diff --git a/module/action/lang/en.php b/module/action/lang/en.php index 2c87a96c7d..f8218e2019 100755 --- a/module/action/lang/en.php +++ b/module/action/lang/en.php @@ -68,7 +68,7 @@ $lang->action->hasCreatedTask = 'Tasks have been created in this parent phas $lang->action->hasDeletedParent = 'Restoring this phase requires restoring the deleted parent phase %s at the same time.'; $lang->action->hasChangedAttr = "After recovery, the phase type will be adjusted to '%s' according to the parent phase type."; $lang->action->whetherToRestore = 'Confirm to restore?'; -$lang->action->undeleteModuleTip = 'Once the subdirectory has been restored, its parent directory will also be automatically recovered and synchronized.'; +$lang->action->undeleteModuleTip = 'Once the subdirectory has been restored, its parent directory will also be automatically recovered and synchronized, would you like to proceed with restoring the directory?'; $lang->action->undeleteTaskTip = 'The execution associated with this task has been removed. If you choose to restore it, the task will no longer be visible. Would you like to proceed with restoring the task?'; $lang->action->hasOtherType = array(); diff --git a/module/action/lang/fr.php b/module/action/lang/fr.php index f9c2b22327..ae5af00ede 100644 --- a/module/action/lang/fr.php +++ b/module/action/lang/fr.php @@ -68,7 +68,7 @@ $lang->action->hasCreatedTask = 'Tasks have been created in this parent phas $lang->action->hasDeletedParent = 'Restoring this phase requires restoring the deleted parent phase %s at the same time.'; $lang->action->hasChangedAttr = "After recovery, the phase type will be adjusted to '%s' according to the parent phase type."; $lang->action->whetherToRestore = 'Confirm to restore?'; -$lang->action->undeleteModuleTip = 'Once the subdirectory has been restored, its parent directory will also be automatically recovered and synchronized.'; +$lang->action->undeleteModuleTip = 'Once the subdirectory has been restored, its parent directory will also be automatically recovered and synchronized, would you like to proceed with restoring the directory?'; $lang->action->undeleteTaskTip = 'The execution associated with this task has been removed. If you choose to restore it, the task will no longer be visible. Would you like to proceed with restoring the task?'; $lang->action->hasOtherType = array(); diff --git a/module/action/lang/zh-cn.php b/module/action/lang/zh-cn.php index f8e11a85dd..0390988105 100755 --- a/module/action/lang/zh-cn.php +++ b/module/action/lang/zh-cn.php @@ -68,7 +68,7 @@ $lang->action->hasCreatedTask = '该父阶段已创建任务,无法恢复 $lang->action->hasDeletedParent = '恢复该阶段需要同时恢复已删除的父阶段%s,'; $lang->action->hasChangedAttr = '恢复后阶段类型将根据父阶段类型均调整为“%s”,'; $lang->action->whetherToRestore = '是否恢复?'; -$lang->action->undeleteModuleTip = '子目录恢复后,会同步恢复他的上级目录。'; +$lang->action->undeleteModuleTip = '子目录恢复后,会同步恢复他的上级目录,确认要恢复吗?'; $lang->action->undeleteTaskTip = '该任务所属执行已删除,还原后不能看到该任务,是否继续还原任务?'; $lang->action->hasOtherType = array(); diff --git a/module/action/test/model/getdynamicbysearch.php b/module/action/test/model/getdynamicbysearch.php index 82e982b8b5..206f878dd1 100755 --- a/module/action/test/model/getdynamicbysearch.php +++ b/module/action/test/model/getdynamicbysearch.php @@ -3,14 +3,6 @@ include dirname(__FILE__, 5) . '/test/lib/init.php'; include dirname(__FILE__, 2) . '/action.class.php'; -zdTable('action')->config('action')->gen(90); -zdTable('doclib')->gen(1); -zdTable('doc')->gen(1); -zdTable('product')->gen(1); -zdTable('userquery')->config('userquery')->gen(1); - -su('admin'); - /** title=测试 actionModel->getDynamicBySearch(); @@ -18,31 +10,45 @@ timeout=0 cid=1 - 获取排序为date倒序的所有动态 - - 第0条的id属性 @64 - - 第0条的objectType属性 @testsuite - - 第1条的id属性 @32 - - 第1条的objectType属性 @testcase + - 第0条的id属性 @90 + - 第0条的objectType属性 @entry + - 第1条的id属性 @60 + - 第1条的objectType属性 @doclib - 获取排序为date正序的所有动态 - - 第0条的id属性 @65 - - 第0条的objectType属性 @caselib - - 第1条的id属性 @33 - - 第1条的objectType属性 @case -- 获取排序为date倒序的今年之后的动态 - - 第0条的id属性 @64 - - 第0条的objectType属性 @testsuite - - 第1条的id属性 @32 - - 第1条的objectType属性 @testcase -- 获取排序为date倒序的今天之前的动态 - - 第0条的id属性 @63 - - 第0条的objectType属性 @module + - 第0条的id属性 @61 + - 第0条的objectType属性 @todo - 第1条的id属性 @31 - 第1条的objectType属性 @bug +- 获取排序为date倒序的今年之后的动态 + - 第0条的id属性 @90 + - 第0条的objectType属性 @entry + - 第1条的id属性 @60 + - 第1条的objectType属性 @doclib +- 获取排序为date倒序的今天之前的动态 + - 第0条的id属性 @89 + - 第0条的objectType属性 @testreport + - 第1条的id属性 @59 + - 第1条的objectType属性 @doc - 获取排序为date倒序的今年之后的动态 - 第0条的id属性 @1 - 第0条的objectType属性 @product */ +zdTable('action')->config('action')->gen(90); +zdTable('doclib')->gen(1); +zdTable('doc')->gen(1); +zdTable('lang')->gen(0); +zdTable('product')->gen(1); +zdTable('userquery')->config('userquery')->gen(1); + +global $lang, $app; +$lang->SRCommon = '研发需求'; +$lang->URCommon = '用户需求'; +$app->loadLang('action'); + +su('admin'); + $queryID = array(0, 1); $orderByList = array('date_desc,id_desc', 'date_asc,id_desc'); $limit = 50; @@ -51,8 +57,8 @@ $directionList = array('next', 'pre'); $action = new actionTest(); -r($action->getDynamicBySearchTest($queryID[0], $orderByList[0], $limit, $dateList[0], $directionList[0])) && p('0:id,objectType;1:id,objectType') && e('64,testsuite;32,testcase'); // 获取排序为date倒序的所有动态 -r($action->getDynamicBySearchTest($queryID[0], $orderByList[1], $limit, $dateList[0], $directionList[0])) && p('0:id,objectType;1:id,objectType') && e('65,caselib;33,case'); // 获取排序为date正序的所有动态 -r($action->getDynamicBySearchTest($queryID[0], $orderByList[0], $limit, $dateList[0], $directionList[1])) && p('0:id,objectType;1:id,objectType') && e('64,testsuite;32,testcase'); // 获取排序为date倒序的今年之后的动态 -r($action->getDynamicBySearchTest($queryID[0], $orderByList[0], $limit, $dateList[1], $directionList[0])) && p('0:id,objectType;1:id,objectType') && e('63,module;31,bug'); // 获取排序为date倒序的今天之前的动态 -r($action->getDynamicBySearchTest($queryID[1], $orderByList[0], $limit, $dateList[0], $directionList[1])) && p('0:id,objectType') && e('1,product'); // 获取排序为date倒序的今年之后的动态 \ No newline at end of file +r($action->getDynamicBySearchTest($queryID[0], $orderByList[0], $limit, $dateList[0], $directionList[0])) && p('0:id,objectType;1:id,objectType') && e('90,entry;60,doclib'); // 获取排序为date倒序的所有动态 +r($action->getDynamicBySearchTest($queryID[0], $orderByList[1], $limit, $dateList[0], $directionList[0])) && p('0:id,objectType;1:id,objectType') && e('61,todo;31,bug'); // 获取排序为date正序的所有动态 +r($action->getDynamicBySearchTest($queryID[0], $orderByList[0], $limit, $dateList[0], $directionList[1])) && p('0:id,objectType;1:id,objectType') && e('90,entry;60,doclib'); // 获取排序为date倒序的今年之后的动态 +r($action->getDynamicBySearchTest($queryID[0], $orderByList[0], $limit, $dateList[1], $directionList[0])) && p('0:id,objectType;1:id,objectType') && e('89,testreport;59,doc'); // 获取排序为date倒序的今天之前的动态 +r($action->getDynamicBySearchTest($queryID[1], $orderByList[0], $limit, $dateList[0], $directionList[1])) && p('0:id,objectType') && e('1,product'); // 获取排序为date倒序的今年之后的动态 diff --git a/module/action/test/model/getobjectlabel.php b/module/action/test/model/getobjectlabel.php index 3c1af162a6..56e9998344 100755 --- a/module/action/test/model/getobjectlabel.php +++ b/module/action/test/model/getobjectlabel.php @@ -36,6 +36,12 @@ cid=1 */ +global $lang, $app; +$lang->SRCommon = '研发需求'; +$lang->URCommon = '用户需求'; +$app::$loadedLangs = array(); +$app->loadLang('action'); + $objectType = array('product', 'story', 'productplan', 'release', 'project', 'task', 'build', 'bug', 'testcase', 'case', 'testtask', 'user', 'doc', 'doclib', 'todo', 'branch', 'module', 'testsuite', 'caselib', 'testreport', 'entry', 'webhook', 'review'); $objectId = array(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,25); $actionType = array('common', 'extra', 'opened', 'created', 'changed', 'edited', 'assigned', 'closed', 'deleted', 'deletedfile', 'editfile', 'erased', 'undeleted', 'hidden', 'commented', 'activated', 'blocked', 'moved', 'confirmed', 'caseconfirmed', 'bugconfirmed', 'frombug', 'started', 'delayed'); @@ -65,4 +71,4 @@ r($action->getObjectLabelTest($objectType[19], $objectId[19], $actionType[19], $ r($action->getObjectLabelTest($objectType[20], $objectId[20], $actionType[20], $requirements)) && p() && e('应用|entry|browse|'); // 测试获取objectType entry objectId 21的动态信息 r($action->getObjectLabelTest($objectType[21], $objectId[21], $actionType[21], $requirements)) && p() && e('Webhook|webhook|browse|'); // 测试获取objectType webhook objectId 22的动态信息 r($action->getObjectLabelTest($objectType[22], $objectId[22], $actionType[22], $requirements)) && p() && e('review'); // 测试获取objectType review objectId 23的动态信息 -r($action->getObjectLabelTest($objectType[1], $objectId[23], $actionType[23], $requirements)) && p() && e('用户需求|story|view|storyID=%s'); // 测试获取objectType story objectId 25的动态信息 \ No newline at end of file +r($action->getObjectLabelTest($objectType[1], $objectId[23], $actionType[23], $requirements)) && p() && e('用户需求|story|view|storyID=%s'); // 测试获取objectType story objectId 25的动态信息 diff --git a/module/action/test/model/restorestages.php b/module/action/test/model/restorestages.php index a168680a57..cbb8b6d8c7 100755 --- a/module/action/test/model/restorestages.php +++ b/module/action/test/model/restorestages.php @@ -5,6 +5,7 @@ include dirname(__FILE__, 2) . '/action.class.php'; zdTable('user')->gen(5); su('admin'); +zdTable('stage')->gen(5); zdTable('project')->config('project')->gen(5); zdTable('action')->config('action')->gen(4); @@ -22,4 +23,4 @@ $action = new actionTest(); $hasDeleted = $action->restoreStagesTest(array(2 => 2, 3 => 3), array(1, 2)); -r($hasDeleted) && p('') && e('1'); // 测试还原id为2和3的阶段 \ No newline at end of file +r($hasDeleted) && p('') && e('1'); // 测试还原id为2和3的阶段 diff --git a/module/action/test/model/transformactions.php b/module/action/test/model/transformactions.php index 158e2e141f..552a35bb09 100755 --- a/module/action/test/model/transformactions.php +++ b/module/action/test/model/transformactions.php @@ -60,11 +60,16 @@ cid=101 */ +global $lang, $app; +$lang->SRCommon = '研发需求'; +$lang->URCommon = '用户需求'; +$app->loadLang('action'); + $actions = array('1,2,3,4,5', '26,27,28,29,30', '51,52,53,54,55', '71,72,73,74,75'); $action = new actionTest(); -r($action->transformActionsTest($actions[0])) && p('1:objectName;2:objectName;3:objectName;4:objectName;5:objectName') && e('正常产品1;用户需求1;1.0;产品正常的正常的发布1;项目集1'); // 测试转换动态1 2 3 4 5 +r($action->transformActionsTest($actions[0])) && p('1:objectName;2:objectName;3:objectName;4:objectName;5:objectName') && e('正常产品1;用户需求1;1.0;产品正常的正常的发布1;项目集1'); // 测试转换动态1 2 3 4 5 r($action->transformActionsTest($actions[1])) && p('26:objectName;27:objectName;28:objectName;29:objectName;30:objectName') && e('开发任务11;项目版本版本1;BUG1;这个是测试用例1;这个是测试用例1'); // 测试转换动态26 27 28 29 30 r($action->transformActionsTest($actions[2])) && p('51:objectName;52:objectName;53:objectName;54:objectName;55:objectName') && e('测试单1;admin;文档标题1;产品主库;自定义1的待办'); // 测试转换动态51 52 53 54 55 -r($action->transformActionsTest($actions[3])) && p('71:objectName;72:objectName;73:objectName;74:objectName;75:objectName') && e('分支1;这是一个模块1;这是测试套件名称1;这是测试套件名称1;2023-09-19EXECUTION#101 迭代1 测试报告'); // 测试转换动态71 72 73 74 75 \ No newline at end of file +r($action->transformActionsTest($actions[3])) && p('71:objectName;72:objectName;73:objectName;74:objectName;75:objectName') && e('分支1;这是一个模块1;这是测试套件名称1;这是测试套件名称1;2023-09-19EXECUTION#101 迭代1 测试报告'); // 测试转换动态71 72 73 74 75 diff --git a/module/aiapp/control.php b/module/aiapp/control.php index f01d9f1858..2dd024c3ba 100644 --- a/module/aiapp/control.php +++ b/module/aiapp/control.php @@ -140,8 +140,6 @@ class aiapp extends control else if($category === 'latest') $miniPrograms = $this->aiapp->getLatestMiniPrograms($pager); else $miniPrograms = $this->ai->getMiniPrograms($category, 'active', 'createdDate_desc', $pager); - if(empty($miniPrograms)) return $this->locate($this->createLink('aiapp', 'square', array('category' => 'discovery'))); - $squareCategoryArray = $this->aiapp->getSquareCategoryArray(); $usedCategoryArray = $this->aiapp->getUsedCategoryArray(); diff --git a/module/aiapp/js/square.ui.js b/module/aiapp/js/square.ui.js index cefcd64d58..ee104baa38 100644 --- a/module/aiapp/js/square.ui.js +++ b/module/aiapp/js/square.ui.js @@ -19,6 +19,5 @@ window.aiSquare.handleStarBtnClick = function(e) $btn.children('img').attr('src', 'static/svg/star-empty.svg'); $btn.attr('data-url', $btn.attr('data-url').replace('true', 'false')); } - loadCurrentPage({selector : '#mainMenu'}); }, 'json'); }; diff --git a/module/aiapp/model.php b/module/aiapp/model.php index 64e267f12b..4d8a368f1c 100644 --- a/module/aiapp/model.php +++ b/module/aiapp/model.php @@ -173,7 +173,6 @@ class aiappModel extends model if($collectedIDs === null) $collectedIDs = $this->getCollectedMiniProgramIDs($this->app->user->id); if($latestSum === null) $latestSum = $this->countLatestMiniPrograms(); - if(empty($collectedIDs)) unset($squareCategoryArray['collection']); if($latestSum == 0) unset($squareCategoryArray['latest']); return $squareCategoryArray; } diff --git a/module/aiapp/ui/square.html.php b/module/aiapp/ui/square.html.php index f9016525ed..0d60f7bd99 100644 --- a/module/aiapp/ui/square.html.php +++ b/module/aiapp/ui/square.html.php @@ -125,13 +125,31 @@ $miniProgramCard = function($miniProgram) use ($categoryList, $collectedIDs, $sh ) ); }; - -div( +if(empty($miniPrograms)) +{ + panel( + div( + setClass('dtable-empty-tip'), + div( + setClass('row gap-4 items-center'), + span + ( + setClass('text-gray'), + $lang->noData + ), + ) + ) + ); +} +else +{ + div( setClass('miniprogram-container'), array_map($miniProgramCard, $miniPrograms), -); + ); +} -div( +empty($miniPrograms) ? null : div( setClass('pager-container'), pager(set(usePager())) ); diff --git a/module/block/lang/de.php b/module/block/lang/de.php index 62283aeb94..d1df1bf464 100644 --- a/module/block/lang/de.php +++ b/module/block/lang/de.php @@ -147,6 +147,7 @@ $lang->block->default['scrumproject'][] = array('title' => "{$lang->executionCom $lang->block->default['scrumproject'][] = array('title' => 'Dynamic', 'module' => 'scrumproject', 'code' => 'projectdynamic', 'width' => '1'); $lang->block->default['kanbanproject'] = $lang->block->default['scrumproject']; +unset($lang->block->default['kanbanproject'][2]); $lang->block->default['agileplusproject'] = $lang->block->default['scrumproject']; $lang->block->default['waterfallproject'][] = array('title' => "{$lang->projectCommon}Plan", 'module' => 'waterfallproject', 'code' => 'waterfallgantt', 'width' => '2'); diff --git a/module/block/lang/en.php b/module/block/lang/en.php index ef5c4befd2..f75dc5062c 100644 --- a/module/block/lang/en.php +++ b/module/block/lang/en.php @@ -147,6 +147,7 @@ $lang->block->default['scrumproject'][] = array('title' => "{$lang->executionCom $lang->block->default['scrumproject'][] = array('title' => 'Dynamic', 'module' => 'scrumproject', 'code' => 'projectdynamic', 'width' => '1'); $lang->block->default['kanbanproject'] = $lang->block->default['scrumproject']; +unset($lang->block->default['kanbanproject'][2]); $lang->block->default['agileplusproject'] = $lang->block->default['scrumproject']; $lang->block->default['waterfallproject'][] = array('title' => "{$lang->projectCommon}Plan", 'module' => 'waterfallproject', 'code' => 'waterfallgantt', 'width' => '2'); diff --git a/module/block/lang/fr.php b/module/block/lang/fr.php index 351753849b..b039e83738 100644 --- a/module/block/lang/fr.php +++ b/module/block/lang/fr.php @@ -147,6 +147,7 @@ $lang->block->default['scrumproject'][] = array('title' => "{$lang->executionCom $lang->block->default['scrumproject'][] = array('title' => 'Dynamic', 'module' => 'scrumproject', 'code' => 'projectdynamic', 'width' => '1'); $lang->block->default['kanbanproject'] = $lang->block->default['scrumproject']; +unset($lang->block->default['kanbanproject'][2]); $lang->block->default['agileplusproject'] = $lang->block->default['scrumproject']; $lang->block->default['waterfallproject'][] = array('title' => "{$lang->projectCommon}Plan", 'module' => 'waterfallproject', 'code' => 'waterfallgantt', 'width' => '2'); diff --git a/module/block/lang/zh-cn.php b/module/block/lang/zh-cn.php index 65cbf83d94..c1363e7067 100644 --- a/module/block/lang/zh-cn.php +++ b/module/block/lang/zh-cn.php @@ -147,6 +147,7 @@ $lang->block->default['scrumproject'][] = array('title' => "{$lang->executionCom $lang->block->default['scrumproject'][] = array('title' => '最新动态', 'module' => 'scrumproject', 'code' => 'projectdynamic', 'width' => '1'); $lang->block->default['kanbanproject'] = $lang->block->default['scrumproject']; +unset($lang->block->default['kanbanproject'][2]); $lang->block->default['agileplusproject'] = $lang->block->default['scrumproject']; $lang->block->default['waterfallproject'][] = array('title' => "{$lang->projectCommon}计划", 'module' => 'waterfallproject', 'code' => 'waterfallgantt', 'width' => '2'); @@ -557,7 +558,7 @@ $lang->block->projectoverview->thisYear = '今年完成'; $lang->block->projectoverview->lastThreeYear = '近三年完成的项目数量分布'; $lang->block->projectstatistic = new stdclass(); -$lang->block->projectstatistic->story = '需求'; +$lang->block->projectstatistic->story = $lang->SRCommon; $lang->block->projectstatistic->cost = '投入'; $lang->block->projectstatistic->task = '任务'; $lang->block->projectstatistic->bug = 'Bug'; diff --git a/module/block/ui/assigntomeblock.html.php b/module/block/ui/assigntomeblock.html.php index 5805c844c8..a697505cc0 100644 --- a/module/block/ui/assigntomeblock.html.php +++ b/module/block/ui/assigntomeblock.html.php @@ -119,6 +119,8 @@ foreach($hasViewPriv as $type => $bool) set::onRenderCell(jsRaw('window.renderCell')), set::cols(array_values($config->block->{$configType}->dtable->fieldList)), set::data(array_values($data)), + $type == 'bug' ? set::priList($lang->{$type}->priList) : null, + $type == 'bug' ? set::severityList($lang->{$type}->severityList) : null, set::userMap($users) ) ); diff --git a/module/block/ui/bugblock.html.php b/module/block/ui/bugblock.html.php index aaedc243ea..6dd0ca046b 100644 --- a/module/block/ui/bugblock.html.php +++ b/module/block/ui/bugblock.html.php @@ -27,6 +27,8 @@ blockPanel set::bordered(false), set::horzScrollbarPos('inside'), set::fixedLeftWidth('50%'), + set::priList($lang->bug->priList), + set::severityList($lang->bug->severityList), set::cols(array_values($config->block->bug->dtable->fieldList)), set::data(array_values($bugs)) ) diff --git a/module/block/ui/projectstatisticblock.html.php b/module/block/ui/projectstatisticblock.html.php index 740c6eea0f..f3bdc4ba46 100644 --- a/module/block/ui/projectstatisticblock.html.php +++ b/module/block/ui/projectstatisticblock.html.php @@ -142,7 +142,7 @@ if($project) span ( setClass('text-gray'), - $field == 'storyPoints' ? $config->block->storyUnitList[$config->custom->hourPoint] : $lang->block->projectstatistic->{$unit} + $field == 'storyPoints' ? $config->block->storyUnitList[$config->custom->hourPoint] : zget($lang->block->projectstatistic, $unit, '') ) ); } @@ -242,7 +242,7 @@ if($project) 'help' ), ), - span(set::class('px-1'), ':'), + span(setClass('px-1'), ':'), span ( setClass('font-bold text-black mr-1'), @@ -270,7 +270,7 @@ if($project) 'help' ), ), - span(set::class('px-1'), ':'), + span(setClass('px-1'), ':'), span ( setClass('font-bold text-black mr-1'), @@ -298,7 +298,7 @@ if($project) 'help' ), ), - span(set::class('px-1'), ':'), + span(setClass('px-1'), ':'), span ( setClass('font-bold text-black mr-1'), @@ -343,7 +343,7 @@ if($project) 'help' ), ), - span(set::class('px-1'), ':'), + span(setClass('px-1'), ':'), span ( setClass('font-bold text-black mr-1'), @@ -371,7 +371,7 @@ if($project) 'help' ), ), - span(set::class('px-1'), ':'), + span(setClass('px-1'), ':'), span ( setClass('font-bold text-black mr-1'), @@ -399,7 +399,7 @@ if($project) 'help' ), ), - span(set::class('px-1'), ':'), + span(setClass('px-1'), ':'), span ( setClass('font-bold text-black mr-1'), diff --git a/module/block/ui/waterfallreportblock.html.php b/module/block/ui/waterfallreportblock.html.php index 3548e75b0f..ffc37537f7 100644 --- a/module/block/ui/waterfallreportblock.html.php +++ b/module/block/ui/waterfallreportblock.html.php @@ -234,7 +234,7 @@ if($project) 'help' ), ), - span(set::class('px-1'), ':'), + span(setClass('px-1'), ':'), span ( setClass('font-bold text-black mr-1'), @@ -262,7 +262,7 @@ if($project) 'help' ), ), - span(set::class('px-1'), ':'), + span(setClass('px-1'), ':'), span ( setClass('font-bold text-black mr-1'), @@ -290,7 +290,7 @@ if($project) 'help' ), ), - span(set::class('px-1'), ':'), + span(setClass('px-1'), ':'), span ( setClass('font-bold text-black mr-1'), @@ -335,7 +335,7 @@ if($project) 'help' ), ), - span(set::class('px-1'), ':'), + span(setClass('px-1'), ':'), span ( setClass('font-bold text-black mr-1'), @@ -363,7 +363,7 @@ if($project) 'help' ), ), - span(set::class('px-1'), ':'), + span(setClass('px-1'), ':'), span ( setClass('font-bold text-black mr-1'), @@ -391,7 +391,7 @@ if($project) 'help' ), ), - span(set::class('px-1'), ':'), + span(setClass('px-1'), ':'), span ( setClass('font-bold text-black mr-1'), diff --git a/module/bug/js/batchcreate.ui.js b/module/bug/js/batchcreate.ui.js index 81eccdcae0..b1dae61ed4 100644 --- a/module/bug/js/batchcreate.ui.js +++ b/module/bug/js/batchcreate.ui.js @@ -82,7 +82,7 @@ function loadProductExecutionsByProject(event) const $currentRow = $target.closest('tr'); // Currenr batch form row element. const projectID = $target.val(); // Project ID. const productID = $('[name="product"]').val(); // Product ID (from hidden form control). - const branch = $currentRow.find('.form-batch-input[data-name="branch"]').val() || '0'; // Branch ID (from same row). + const branch = $currentRow.find('.form-batch-control[data-name="branch"] input').val() || '0'; // Branch ID (from same row). /* Get executions with ajax request. */ $.getJSON($.createLink('product', 'ajaxGetExecutionsByProject', 'productID=' + productID + '&projectID=' + projectID + '&branch=' + branch), function(data) diff --git a/module/bug/js/view.ui.js b/module/bug/js/view.ui.js index e83b9582cd..29ff7feb8a 100644 --- a/module/bug/js/view.ui.js +++ b/module/bug/js/view.ui.js @@ -34,7 +34,7 @@ $(document).on('click', '#toTaskButton', function() function changeTaskProjects(event) { - const projectID = event != undefined ? $(event.target).val() : $('[name="project"]').val(); + const projectID = event != undefined ? $(event.target).val() : $('[name="taskProjects"]').val(); const link = $.createLink('product', 'ajaxGetExecutions', 'productID=' + productID + '&projectID=' + projectID +'&branch=' + branchID + '&number=&executionID=0&from=bugToTask'); $.get(link, function(data) { diff --git a/module/bug/test/model/getproductbugs.php b/module/bug/test/model/getproductbugs.php index 1583e5b82a..1edd4ba350 100644 --- a/module/bug/test/model/getproductbugs.php +++ b/module/bug/test/model/getproductbugs.php @@ -3,16 +3,55 @@ include dirname(__FILE__, 5) . '/test/lib/init.php'; include dirname(__FILE__, 2) . '/bug.class.php'; -zdTable('bug')->gen(40); - /** title=bugModel->getProductBugs(); cid=1 pid=1 +- 测试获取产品ID为1 2 3 的bug @1,2,3,4,5,6,7,8,9 + +- 测试获取产品ID为1 2 3 的bug @1,2,3,4,5,6,7,8,9 + +- 测试获取产品ID为1 2 3 解决日期为今天的bug @4 +- 测试获取产品ID为1 2 3 解决日期大于上周 小于下周的bug @3,4,5,9 + +- 测试获取产品ID为1 2 3 创建日期为今天的bug @5 +- 测试获取产品ID为1 2 3 创建日期大于上周 小于下周的bug @4,5,6 + +- 测试获取产品ID为4 5 6 的bug @10,11,12,13,14,15,16,17,18 + +- 测试获取产品ID为4 5 6 的bug @10,11,12,13,14,15,16,17,18 + +- 测试获取产品ID为4 5 6 解决日期为今天的bug @10,16 + +- 测试获取产品ID为4 5 6 解决日期大于上周 小于下周的bug @10,11,15,16,17 + +- 测试获取产品ID为4 5 6 创建日期为今天的bug @13 +- 测试获取产品ID为4 5 6 创建日期大于上周 小于下周的bug @12,13,14 + +- 测试获取产品ID为7 8 9 的bug @19,20,21,22,23,24,25,26,27 + +- 测试获取产品ID为7 8 9 的bug @19,20,21,22,23,24,25,26,27 + +- 测试获取产品ID为7 8 9 解决日期为今天的bug @22 +- 测试获取产品ID为7 8 9 解决日期大于上周 小于下周的bug @21,22,23,27 + +- 测试获取产品ID为7 8 9 创建日期为今天的bug @21 +- 测试获取产品ID为7 8 9 创建日期大于上周 小于下周的bug @20,21,22 + +- 测试获取产品ID为0的bug @0 +- 测试获取产品ID为0的bug @0 +- 测试获取产品ID为0解决日期为今天的bug @0 +- 测试获取产品ID为0解决日期大于上周 小于下周的bug @0 +- 测试获取产品ID为0创建日期为今天的bug @0 +- 测试获取产品ID为0创建日期大于上周 小于下周的bug @0 + */ +zdTable('product')->gen(10); +zdTable('bug')->config('bug_product')->gen(40); + $productIdList = array('1,2,3', '4,5,6', '7,8,9', '0'); $typeList = array('', 'resolved', 'opened'); $beginList = array('today', 'lastweek'); @@ -20,30 +59,30 @@ $endList = array('today', 'nextweek'); $bug = new bugTest(); -r($bug->getProductBugsTest($productIdList[0], $typeList[0], $beginList[0], $endList[0])) && p() && e('1,2,3,4,5,6,7,8,9'); // 测试获取产品 1 2 3 类型 空 开始日期 今天 结束日期 今天 的bug -r($bug->getProductBugsTest($productIdList[0], $typeList[0], $beginList[1], $endList[1])) && p() && e('1,2,3,4,5,6,7,8,9'); // 测试获取产品 1 2 3 类型 空 开始日期 上周 结束日期 下周 的bug -r($bug->getProductBugsTest($productIdList[0], $typeList[1], $beginList[0], $endList[0])) && p() && e('8'); // 测试获取产品 1 2 3 类型 resolved 开始日期 今天 结束日期 今天 的bug -r($bug->getProductBugsTest($productIdList[0], $typeList[1], $beginList[1], $endList[1])) && p() && e('1,2,3,4,5,6,7,8,9'); // 测试获取产品 1 2 3 类型 resolved 开始日期 上周 结束日期 下周 的bug -r($bug->getProductBugsTest($productIdList[0], $typeList[2], $beginList[0], $endList[0])) && p() && e('0'); // 测试获取产品 1 2 3 类型 opened 开始日期 今天 结束日期 今天 的bug -r($bug->getProductBugsTest($productIdList[0], $typeList[2], $beginList[1], $endList[1])) && p() && e('0'); // 测试获取产品 1 2 3 类型 opened 开始日期 上周 结束日期 下周 的bug +r($bug->getProductBugsTest($productIdList[0], $typeList[0], $beginList[0], $endList[0])) && p() && e('1,2,3,4,5,6,7,8,9'); // 测试获取产品ID为1 2 3 的bug +r($bug->getProductBugsTest($productIdList[0], $typeList[0], $beginList[1], $endList[1])) && p() && e('1,2,3,4,5,6,7,8,9'); // 测试获取产品ID为1 2 3 的bug +r($bug->getProductBugsTest($productIdList[0], $typeList[1], $beginList[0], $endList[0])) && p() && e('4'); // 测试获取产品ID为1 2 3 解决日期为今天的bug +r($bug->getProductBugsTest($productIdList[0], $typeList[1], $beginList[1], $endList[1])) && p() && e('3,4,5,9'); // 测试获取产品ID为1 2 3 解决日期大于上周 小于下周的bug +r($bug->getProductBugsTest($productIdList[0], $typeList[2], $beginList[0], $endList[0])) && p() && e('5'); // 测试获取产品ID为1 2 3 创建日期为今天的bug +r($bug->getProductBugsTest($productIdList[0], $typeList[2], $beginList[1], $endList[1])) && p() && e('4,5,6'); // 测试获取产品ID为1 2 3 创建日期大于上周 小于下周的bug -r($bug->getProductBugsTest($productIdList[1], $typeList[0], $beginList[0], $endList[0])) && p() && e('10,11,12,13,14,15,16,17,18'); // 测试获取产品 4 5 6 类型 空 开始日期 今天 结束日期 今天 的bug -r($bug->getProductBugsTest($productIdList[1], $typeList[0], $beginList[1], $endList[1])) && p() && e('10,11,12,13,14,15,16,17,18'); // 测试获取产品 4 5 6 类型 空 开始日期 上周 结束日期 下周 的bug -r($bug->getProductBugsTest($productIdList[1], $typeList[1], $beginList[0], $endList[0])) && p() && e('0'); // 测试获取产品 4 5 6 类型 resolved 开始日期 今天 结束日期 今天 的bug -r($bug->getProductBugsTest($productIdList[1], $typeList[1], $beginList[1], $endList[1])) && p() && e('10,11,12,13,14,15'); // 测试获取产品 4 5 6 类型 resolved 开始日期 上周 结束日期 下周 的bug -r($bug->getProductBugsTest($productIdList[1], $typeList[2], $beginList[0], $endList[0])) && p() && e('0'); // 测试获取产品 4 5 6 类型 opened 开始日期 今天 结束日期 今天 的bug -r($bug->getProductBugsTest($productIdList[1], $typeList[2], $beginList[1], $endList[1])) && p() && e('0'); // 测试获取产品 4 5 6 类型 opened 开始日期 上周 结束日期 下周 的bug +r($bug->getProductBugsTest($productIdList[1], $typeList[0], $beginList[0], $endList[0])) && p() && e('10,11,12,13,14,15,16,17,18'); // 测试获取产品ID为4 5 6 的bug +r($bug->getProductBugsTest($productIdList[1], $typeList[0], $beginList[1], $endList[1])) && p() && e('10,11,12,13,14,15,16,17,18'); // 测试获取产品ID为4 5 6 的bug +r($bug->getProductBugsTest($productIdList[1], $typeList[1], $beginList[0], $endList[0])) && p() && e('10,16'); // 测试获取产品ID为4 5 6 解决日期为今天的bug +r($bug->getProductBugsTest($productIdList[1], $typeList[1], $beginList[1], $endList[1])) && p() && e('10,11,15,16,17'); // 测试获取产品ID为4 5 6 解决日期大于上周 小于下周的bug +r($bug->getProductBugsTest($productIdList[1], $typeList[2], $beginList[0], $endList[0])) && p() && e('13'); // 测试获取产品ID为4 5 6 创建日期为今天的bug +r($bug->getProductBugsTest($productIdList[1], $typeList[2], $beginList[1], $endList[1])) && p() && e('12,13,14'); // 测试获取产品ID为4 5 6 创建日期大于上周 小于下周的bug -r($bug->getProductBugsTest($productIdList[2], $typeList[0], $beginList[0], $endList[0])) && p() && e('19,20,21,22,23,24,25,26,27'); // 测试获取产品 7 8 9 类型 空 开始日期 今天 结束日期 今天 的bug -r($bug->getProductBugsTest($productIdList[2], $typeList[0], $beginList[1], $endList[1])) && p() && e('19,20,21,22,23,24,25,26,27'); // 测试获取产品 7 8 9 类型 空 开始日期 上周 结束日期 下周 的bug -r($bug->getProductBugsTest($productIdList[2], $typeList[1], $beginList[0], $endList[0])) && p() && e('0'); // 测试获取产品 7 8 9 类型 resolved 开始日期 今天 结束日期 今天 的bug -r($bug->getProductBugsTest($productIdList[2], $typeList[1], $beginList[1], $endList[1])) && p() && e('22,23,24,25,26,27'); // 测试获取产品 7 8 9 类型 resolved 开始日期 上周 结束日期 下周 的bug -r($bug->getProductBugsTest($productIdList[2], $typeList[2], $beginList[0], $endList[0])) && p() && e('0'); // 测试获取产品 7 8 9 类型 opened 开始日期 今天 结束日期 今天 的bug -r($bug->getProductBugsTest($productIdList[2], $typeList[2], $beginList[1], $endList[1])) && p() && e('24,25,26,27'); // 测试获取产品 7 8 9 类型 opened 开始日期 上周 结束日期 下周 的bug +r($bug->getProductBugsTest($productIdList[2], $typeList[0], $beginList[0], $endList[0])) && p() && e('19,20,21,22,23,24,25,26,27'); // 测试获取产品ID为7 8 9 的bug +r($bug->getProductBugsTest($productIdList[2], $typeList[0], $beginList[1], $endList[1])) && p() && e('19,20,21,22,23,24,25,26,27'); // 测试获取产品ID为7 8 9 的bug +r($bug->getProductBugsTest($productIdList[2], $typeList[1], $beginList[0], $endList[0])) && p() && e('22'); // 测试获取产品ID为7 8 9 解决日期为今天的bug +r($bug->getProductBugsTest($productIdList[2], $typeList[1], $beginList[1], $endList[1])) && p() && e('21,22,23,27'); // 测试获取产品ID为7 8 9 解决日期大于上周 小于下周的bug +r($bug->getProductBugsTest($productIdList[2], $typeList[2], $beginList[0], $endList[0])) && p() && e('21'); // 测试获取产品ID为7 8 9 创建日期为今天的bug +r($bug->getProductBugsTest($productIdList[2], $typeList[2], $beginList[1], $endList[1])) && p() && e('20,21,22'); // 测试获取产品ID为7 8 9 创建日期大于上周 小于下周的bug -r($bug->getProductBugsTest($productIdList[3], $typeList[0], $beginList[0], $endList[0])) && p() && e('0'); // 测试获取产品 空 类型 空 开始日期 今天 结束日期 今天 的bug -r($bug->getProductBugsTest($productIdList[3], $typeList[0], $beginList[1], $endList[1])) && p() && e('0'); // 测试获取产品 空 类型 空 开始日期 上周 结束日期 下周 的bug -r($bug->getProductBugsTest($productIdList[3], $typeList[1], $beginList[0], $endList[0])) && p() && e('0'); // 测试获取产品 空 类型 resolved 开始日期 今天 结束日期 今天 的bug -r($bug->getProductBugsTest($productIdList[3], $typeList[1], $beginList[1], $endList[1])) && p() && e('0'); // 测试获取产品 空 类型 resolved 开始日期 上周 结束日期 下周 的bug -r($bug->getProductBugsTest($productIdList[3], $typeList[2], $beginList[0], $endList[0])) && p() && e('0'); // 测试获取产品 空 类型 opened 开始日期 今天 结束日期 今天 的bug -r($bug->getProductBugsTest($productIdList[3], $typeList[2], $beginList[1], $endList[1])) && p() && e('0'); // 测试获取产品 空 类型 opened 开始日期 上周 结束日期 下周 的bug +r($bug->getProductBugsTest($productIdList[3], $typeList[0], $beginList[0], $endList[0])) && p() && e('0'); // 测试获取产品ID为0的bug +r($bug->getProductBugsTest($productIdList[3], $typeList[0], $beginList[1], $endList[1])) && p() && e('0'); // 测试获取产品ID为0的bug +r($bug->getProductBugsTest($productIdList[3], $typeList[1], $beginList[0], $endList[0])) && p() && e('0'); // 测试获取产品ID为0解决日期为今天的bug +r($bug->getProductBugsTest($productIdList[3], $typeList[1], $beginList[1], $endList[1])) && p() && e('0'); // 测试获取产品ID为0解决日期大于上周 小于下周的bug +r($bug->getProductBugsTest($productIdList[3], $typeList[2], $beginList[0], $endList[0])) && p() && e('0'); // 测试获取产品ID为0创建日期为今天的bug +r($bug->getProductBugsTest($productIdList[3], $typeList[2], $beginList[1], $endList[1])) && p() && e('0'); // 测试获取产品ID为0创建日期大于上周 小于下周的bug diff --git a/module/bug/test/model/getstatistic.php b/module/bug/test/model/getstatistic.php index 0cea77e54f..6c43c6b920 100755 --- a/module/bug/test/model/getstatistic.php +++ b/module/bug/test/model/getstatistic.php @@ -3,26 +3,37 @@ include dirname(__FILE__, 5) . '/test/lib/init.php'; include dirname(__FILE__, 2) . '/bug.class.php'; -zdTable('bug')->gen(100); - /** title=bugModel->getStatistic(); cid=1 pid=1 +- 查询product id为0创建日期在今天的统计信息第openedDate条的num属性 @0 +- 查询product id为1创建日期在今天的统计信息第openedDate条的num属性 @1 +- 查询product id为1创建日期在今天的统计信息第resolvedDate条的num属性 @1 +- 查询product id为1创建日期在今天的统计信息第closedDate条的num属性 @0 +- 查询product id为11解决日期在今天的统计信息第resolvedDate条的num属性 @1 +- 查询product id为31解决日期在今天的统计信息第resolvedDate条的num属性 @1 +- 查询product id为61解决日期在今天的统计信息第resolvedDate条的num属性 @0 +- 查询product id为91关闭日期在今天的统计信息第closedDate条的num属性 @0 +- 查询product id为10001关闭日期在今天的统计信息第closedDate条的num属性 @0 + */ +zdTable('product')->gen(100); +zdTable('bug')->config('bug_product')->gen(100); + $productIDList = array('1', '11', '31', '61', '91', '10001'); $bug=new bugTest(); r($bug->getStatisticTest()) && p('openedDate:num') && e('0'); // 查询product id为0创建日期在今天的统计信息 -r($bug->getStatisticTest($productIDList[0])) && p('openedDate:num') && e('0'); // 查询product id为1创建日期在今天的统计信息 -r($bug->getStatisticTest($productIDList[0])) && p('resolvedDate:num') && e('0'); // 查询product id为1创建日期在今天的统计信息 +r($bug->getStatisticTest($productIDList[0])) && p('openedDate:num') && e('1'); // 查询product id为1创建日期在今天的统计信息 +r($bug->getStatisticTest($productIDList[0])) && p('resolvedDate:num') && e('1'); // 查询product id为1创建日期在今天的统计信息 r($bug->getStatisticTest($productIDList[0])) && p('closedDate:num') && e('0'); // 查询product id为1创建日期在今天的统计信息 -r($bug->getStatisticTest($productIDList[1])) && p('openedDate:num') && e('1'); // 查询product id为11解决日期在今天的统计信息 -r($bug->getStatisticTest($productIDList[2])) && p('openedDate:num') && e('0'); // 查询product id为31解决日期在今天的统计信息 -r($bug->getStatisticTest($productIDList[3])) && p('openedDate:num') && e('0'); // 查询product id为61解决日期在今天的统计信息 -r($bug->getStatisticTest($productIDList[4])) && p('openedDate:num') && e('0'); // 查询product id为91关闭日期在今天的统计信息 -r($bug->getStatisticTest($productIDList[5])) && p('openedDate:num') && e('0'); // 查询product id为10001关闭日期在今天的统计信息 +r($bug->getStatisticTest($productIDList[1])) && p('resolvedDate:num') && e('1'); // 查询product id为11解决日期在今天的统计信息 +r($bug->getStatisticTest($productIDList[2])) && p('resolvedDate:num') && e('1'); // 查询product id为31解决日期在今天的统计信息 +r($bug->getStatisticTest($productIDList[3])) && p('resolvedDate:num') && e('0'); // 查询product id为61解决日期在今天的统计信息 +r($bug->getStatisticTest($productIDList[4])) && p('closedDate:num') && e('0'); // 查询product id为91关闭日期在今天的统计信息 +r($bug->getStatisticTest($productIDList[5])) && p('closedDate:num') && e('0'); // 查询product id为10001关闭日期在今天的统计信息 diff --git a/module/bug/test/model/yaml/getproductbugs/bug_product.yaml b/module/bug/test/model/yaml/getproductbugs/bug_product.yaml new file mode 100644 index 0000000000..989cabd445 --- /dev/null +++ b/module/bug/test/model/yaml/getproductbugs/bug_product.yaml @@ -0,0 +1,20 @@ +--- +title: zt_bug +author: Gang Liu +version: "1.0" +fields: + - field: resolvedDate + range: "(+3w)-(-3w):-1w" + type: timestamp + prefix: "" + postfix: "" + loop: 0 + format: "YYYY-MM-DD" + - field: openedDate + range: "(+4w)-(-4w):-1w" + type: timestamp + prefix: "" + postfix: "" + loop: 0 + format: "YYYY-MM-DD" +... diff --git a/module/bug/test/model/yaml/getstatistic/bug_product.yaml b/module/bug/test/model/yaml/getstatistic/bug_product.yaml new file mode 100644 index 0000000000..cbe5c59f8a --- /dev/null +++ b/module/bug/test/model/yaml/getstatistic/bug_product.yaml @@ -0,0 +1,20 @@ +--- +title: zt_bug +author: Gang Liu +version: "1.0" +fields: + - field: resolvedDate + range: "(+1w)-(-2w):-1w" + type: timestamp + prefix: "" + postfix: "" + loop: 0 + format: "YYYY-MM-DD" + - field: openedDate + range: "(+1w)-(-2w):-1w" + type: timestamp + prefix: "" + postfix: "" + loop: 0 + format: "YYYY-MM-DD" +... diff --git a/module/bug/test/tao/batchappenddelayeddays.php b/module/bug/test/tao/batchappenddelayeddays.php index abc1ea084c..8d17132f96 100755 --- a/module/bug/test/tao/batchappenddelayeddays.php +++ b/module/bug/test/tao/batchappenddelayeddays.php @@ -3,28 +3,35 @@ include dirname(__FILE__, 5) . '/test/lib/init.php'; include dirname(__FILE__, 2) . '/bug.class.php'; -zdTable('bug')->gen(100); -zdTable('product')->gen(40); -zdTable('user')->gen(10); -zdTable('project')->gen(40); - -su('admin'); - /** title=bugModel->batchAppendDelayedDays(); cid=1 pid=1 +- 检查产品1 中的bug延期天数属性delay @7,1,9 +- 检查产品7 中的bug延期天数属性delay @11,5,13 +- 检查产品18 中的bug延期天数属性delay @9,17,11 +- 检查产品20 中的bug延期天数属性delay @1,9,3 +- 检查产品28 中的bug延期天数属性delay @11,19,13 +- 检查产品33 中的bug延期天数属性delay @19,13,7 + */ +zdTable('product')->gen(40); +zdTable('user')->gen(10); +zdTable('project')->gen(40); +zdTable('bug')->config('bug_product')->gen(100); + +su('admin'); + $productIDList = array(1, 7, 18, 20, 28, 33); $bug = new bugTest(); -r($bug->batchAppendDelayedDaysTest($productIDList[0])) && p('delay', '-') && e('37,35,33'); // 检查deadline在resolvedDate前 产品1 中的bug是否延期 -r($bug->batchAppendDelayedDaysTest($productIDList[1])) && p('delay', '-') && e('1,0,0'); // 检查deadline在resolvedDate后 产品7 中的bug是否延期 -r($bug->batchAppendDelayedDaysTest($productIDList[2])) && p('delay', '-') && e('14,12,10'); // 检查deadline在resolvedDate前 产品18 中的bug是否延期 -r($bug->batchAppendDelayedDaysTest($productIDList[3])) && p('delay', '-') && e('2,0,0'); // 检查deadline在resolvedDate后 产品20 中的bug是否延期 -r($bug->batchAppendDelayedDaysTest($productIDList[4])) && p('delay', '-') && e('12,10,8'); // 检查deadline在resolvedDate前 产品28 中的bug是否延期 -r($bug->batchAppendDelayedDaysTest($productIDList[5])) && p('delay', '-') && e('3,1,0'); // 检查deadline在resolvedDate后 产品33 中的bug是否延期 +r($bug->batchAppendDelayedDaysTest($productIDList[0])) && p('delay', '-') && e('7,1,9'); // 检查产品1 中的bug延期天数 +r($bug->batchAppendDelayedDaysTest($productIDList[1])) && p('delay', '-') && e('11,5,13'); // 检查产品7 中的bug延期天数 +r($bug->batchAppendDelayedDaysTest($productIDList[2])) && p('delay', '-') && e('9,17,11'); // 检查产品18 中的bug延期天数 +r($bug->batchAppendDelayedDaysTest($productIDList[3])) && p('delay', '-') && e('1,9,3'); // 检查产品20 中的bug延期天数 +r($bug->batchAppendDelayedDaysTest($productIDList[4])) && p('delay', '-') && e('11,19,13'); // 检查产品28 中的bug延期天数 +r($bug->batchAppendDelayedDaysTest($productIDList[5])) && p('delay', '-') && e('19,13,7'); // 检查产品33 中的bug延期天数 diff --git a/module/bug/test/tao/yaml/batchappenddelayeddays/bug_product.yaml b/module/bug/test/tao/yaml/batchappenddelayeddays/bug_product.yaml new file mode 100644 index 0000000000..32e672c72c --- /dev/null +++ b/module/bug/test/tao/yaml/batchappenddelayeddays/bug_product.yaml @@ -0,0 +1,20 @@ +--- +title: zt_bug +author: Gang Liu +version: "1.0" +fields: + - field: resolvedDate + range: "(+1w)-(-1w):-1w" + type: timestamp + prefix: "" + postfix: "" + loop: 0 + format: "YYYY-MM-DD" + - field: deadline + range: "(+0w)-(-2w):-1D" + type: timestamp + prefix: "" + postfix: "" + loop: 0 + format: "YYYY-MM-DD" +... diff --git a/module/bug/ui/batchcreate.html.php b/module/bug/ui/batchcreate.html.php index ef42d4223a..7f145d43d3 100644 --- a/module/bug/ui/batchcreate.html.php +++ b/module/bug/ui/batchcreate.html.php @@ -39,7 +39,7 @@ $items[] = array('name' => 'id', 'label' => $lang->idAB, 'control' => 'index', ' if($bugs) $items[] = array('name' => 'uploadImage', 'label' => '', 'control' => 'hidden', 'hidden' => true); /* Field of branch. */ -if($product->type != 'normal') $items[] = array('name' => 'branch', 'label' => $lang->product->branchName[$product->type], 'control' => 'picker', 'items' => $branches, 'value' => $branch, 'width' => '200px', 'ditto' => true); +if($product->type != 'normal') $items[] = array('name' => 'branch', 'label' => $lang->product->branchName[$product->type], 'control' => 'picker', 'items' => $branches, 'value' => $branch, 'width' => '200px', 'ditto' => true, 'hidden' => strpos(",{$showFields},", ',branch,') === false); /* Field of module. */ $items[] = array('name' => 'module', 'label' => $lang->bug->module, 'control' => 'picker', 'items' => $moduleOptionMenu, 'value' => $moduleID, 'width' => '200px', 'required' => isset($requiredFields['module']), 'ditto' => true); diff --git a/module/bug/zen.php b/module/bug/zen.php index ffc9f93328..c79a5199af 100644 --- a/module/bug/zen.php +++ b/module/bug/zen.php @@ -1313,6 +1313,11 @@ class bugZen extends bug $assignedToList = array_filter($assignedToList); if(empty($assignedToList)) $assignedToList = $this->user->getPairs('devfirst|noclosed'); } + if(!isset($openedBuilds[$bug->openedBuild])) + { + $build = $this->build->getByID((int)$bug->openedBuild); + if($build) $openedBuilds[$bug->openedBuild] = $build->name; + } if($bug->assignedTo && !isset($assignedToList[$bug->assignedTo]) && $bug->assignedTo != 'closed') { @@ -1512,21 +1517,13 @@ class bugZen extends bug { $customFields[$field] = $this->lang->bug->$field; } - if($product->type != 'normal') $customFields[$product->type] = $this->lang->product->branchName[$product->type]; + if($product->type != 'normal') $customFields['branch'] = $this->lang->product->branchName[$product->type]; if(isset($project->model) && $project->model == 'kanban') $customFields['execution'] = $this->lang->bug->kanban; /* Set display fields. */ $showFields = $this->config->bug->custom->batchCreateFields; - if($product->type != 'normal') - { - $showFields = sprintf($showFields, $product->type); - $showFields = str_replace(array(",branch,", ",platform,"), '', ",$showFields,"); - $showFields = trim($showFields, ','); - } - else - { - $showFields = trim(sprintf($showFields, ''), ','); - } + $showFields = sprintf($showFields, $product->type != 'normal' ? 'branch' : ''); + $showFields = trim($showFields, ','); /* Get titles from uploaded images. */ if(!empty($bugImagesFile)) diff --git a/module/ci/test/model/checkcompilestatus.php b/module/ci/test/model/checkcompilestatus.php index 5166607da2..c051772684 100755 --- a/module/ci/test/model/checkcompilestatus.php +++ b/module/ci/test/model/checkcompilestatus.php @@ -28,4 +28,4 @@ $ci = new ciTest(); r($ci->checkCompileStatusTest(0)) && p('1:name;3:status') && e('构建1,created'); // 不过滤构建任务 r($ci->checkCompileStatusTest(1)) && p('status') && e('created'); // 正常的构建任务 -r($ci->checkCompileStatusTest(4)) && p('status') && e('0'); // 不存在的构建任务 \ No newline at end of file +r($ci->checkCompileStatusTest(4)) && p('status') && e('0'); // 不存在的构建任务 diff --git a/module/ci/test/model/savecompile.php b/module/ci/test/model/savecompile.php index 5712b2fb77..d613c7dc21 100755 --- a/module/ci/test/model/savecompile.php +++ b/module/ci/test/model/savecompile.php @@ -26,9 +26,8 @@ $response = '{"_class":"hudson.model.Queue$LeftItem","actions":[{"_class":"hudso $hasUrl = '{"executable":{"url":"https://jenkinsdev.qc.oop.cc/job/paramsJob/lastBuild/"}}'; $notFound = '404'; +libxml_use_internal_errors(true); $ci = new ciTest(); r($ci->saveCompileTest(1, $notFound)) && p() && e('0'); // 错误的接口信息 - r($ci->saveCompileTest(3, $response)) && p() && e('0'); // 没有有效信息 - -r($ci->saveCompileTest(5, $hasUrl)) && p() && e('1'); // 返回请求URL信息 \ No newline at end of file +r($ci->saveCompileTest(5, $hasUrl)) && p() && e('1'); // 返回请求URL信息 diff --git a/module/ci/test/model/synccompilestatus.php b/module/ci/test/model/synccompilestatus.php index cfb3fb3c41..5ae11cef64 100755 --- a/module/ci/test/model/synccompilestatus.php +++ b/module/ci/test/model/synccompilestatus.php @@ -23,6 +23,7 @@ zdTable('job')->config('job')->gen(10); zdTable('compile')->config('compile')->gen(10); su('admin'); +libxml_use_internal_errors(true); $ci = new ciTest(); r($ci->syncCompileStatusTest(1)) && p('status') && e('created'); // 同步jenkins构建结果 @@ -31,4 +32,4 @@ r($ci->syncCompileStatusTest(2)) && p('status') && e('failed'); // 同步gitlab r($ci->syncCompileStatusTest(4)) && p('status') && e('failure'); // 三次请求没有拿到结果,构建失败 r($ci->syncCompileStatusTest(1, 1)) && p('status') && e('created'); // 同步jenkins构建结果,有合并请求ID -r($ci->syncCompileStatusTest(5, 1)) && p('status') && e('failure'); // 三次请求没有拿到结果,通知MR失败 \ No newline at end of file +r($ci->syncCompileStatusTest(5, 1)) && p('status') && e('failure'); // 三次请求没有拿到结果,通知MR失败 diff --git a/module/common/lang/de.php b/module/common/lang/de.php index e0dff591f7..f38235448b 100644 --- a/module/common/lang/de.php +++ b/module/common/lang/de.php @@ -583,6 +583,9 @@ $lang->noticePasteImg = "Paste images here"; $lang->pasteImgFail = "Failed to paste images. Try again later."; $lang->pasteImgUploading = "Uploading..."; +$lang->visionTips = "You can switch vision here"; +$lang->IKnow = "I know"; + /* Time formats settings. */ if(!defined('DT_DATETIME1')) define('DT_DATETIME1', 'Y-m-d H:i:s'); if(!defined('DT_DATETIME2')) define('DT_DATETIME2', 'y-m-d H:i'); diff --git a/module/common/lang/en.php b/module/common/lang/en.php index e844836319..665399cad6 100644 --- a/module/common/lang/en.php +++ b/module/common/lang/en.php @@ -583,6 +583,9 @@ $lang->noticePasteImg = "You can paste images into the editor."; $lang->pasteImgFail = "Failed to paste images. Try again later."; $lang->pasteImgUploading = "Uploading..."; +$lang->visionTips = "You can switch vision here"; +$lang->IKnow = "I know"; + /* Time formats settings. */ if(!defined('DT_DATETIME1')) define('DT_DATETIME1', 'Y-m-d H:i:s'); if(!defined('DT_DATETIME2')) define('DT_DATETIME2', 'y-m-d H:i'); diff --git a/module/common/lang/fr.php b/module/common/lang/fr.php index 947fcc64e2..b32db1b998 100644 --- a/module/common/lang/fr.php +++ b/module/common/lang/fr.php @@ -583,6 +583,9 @@ $lang->noticePasteImg = "Vous pouvez déposer des images dans l'éditeur."; $lang->pasteImgFail = "Echec lors de la dépose des images. Essayez plus tard."; $lang->pasteImgUploading = "Chargement..."; +$lang->visionTips = "You can switch vision here"; +$lang->IKnow = "I know"; + /* Time formats settings. */ if(!defined('DT_DATETIME1')) define('DT_DATETIME1', 'Y-m-d H:i:s'); if(!defined('DT_DATETIME2')) define('DT_DATETIME2', 'y-m-d H:i'); diff --git a/module/common/lang/zh-cn.php b/module/common/lang/zh-cn.php index a6fccb733d..0e3be0764d 100644 --- a/module/common/lang/zh-cn.php +++ b/module/common/lang/zh-cn.php @@ -583,6 +583,9 @@ $lang->noticePasteImg = "可以在编辑器直接贴图。"; $lang->pasteImgFail = "贴图失败,请稍后重试。"; $lang->pasteImgUploading = "正在上传图片,请稍后..."; +$lang->visionTips = "您可以在这里切换界面"; +$lang->IKnow = "我知道了"; + /* 时间格式设置。*/ if(!defined('DT_DATETIME1')) define('DT_DATETIME1', 'Y-m-d H:i:s'); if(!defined('DT_DATETIME2')) define('DT_DATETIME2', 'y-m-d H:i'); diff --git a/module/common/model.php b/module/common/model.php index e8f6c2600c..eb1a299bf0 100644 --- a/module/common/model.php +++ b/module/common/model.php @@ -594,6 +594,7 @@ class commonModel extends model foreach($menuOrder as $key => $group) { if($group != 'my' && !empty($app->user->rights['acls']['views']) && !isset($app->user->rights['acls']['views'][$group])) continue; // 后台权限分组中没有给导航视图 + if(!isset($lang->mainNav->$group)) continue; $nav = $lang->mainNav->$group; list($title, $currentModule, $currentMethod, $vars) = explode('|', $nav); diff --git a/module/company/js/dynamic.ui.js b/module/company/js/dynamic.ui.js index b7475960b3..977e4a3788 100644 --- a/module/company/js/dynamic.ui.js +++ b/module/company/js/dynamic.ui.js @@ -21,3 +21,8 @@ function toggleCollapse() { $(this).parent().toggleClass('collapsed'); } + +if(browseType == 'bysearch') +{ + window.waitDom('.search-form-toggle', function(){ this[0].click(); }); +} diff --git a/module/custom/config.php b/module/custom/config.php index c599f9834b..ff76431515 100644 --- a/module/custom/config.php +++ b/module/custom/config.php @@ -22,7 +22,7 @@ $config->custom->requiredModules[35] = 'execution'; $config->custom->requiredModules[40] = 'task'; $config->custom->requiredModules[45] = 'build'; -$config->custom->allFeatures = array('program', 'productRR', 'productUR', 'productLine', 'projectScrum', 'projectWaterfall', 'projectKanban', 'projectAgileplus', 'projectWaterfallplus', 'execution', 'qa', 'devops', 'kanban', 'doc', 'report', 'system', 'admin', 'vision', 'ai'); +$config->custom->allFeatures = array('program', 'productRR', 'productUR', 'productLine', 'projectScrum', 'projectWaterfall', 'projectKanban', 'projectAgileplus', 'projectWaterfallplus', 'execution', 'qa', 'devops', 'ai', 'report', 'kanban', 'doc', 'system', 'admin', 'vision'); $config->custom->dataFeatures = array('productUR', 'waterfall', 'waterfallplus'); $config->custom->scrumFeatures = array(); diff --git a/module/datatable/control.php b/module/datatable/control.php index fc52b521ae..1fd8f35611 100644 --- a/module/datatable/control.php +++ b/module/datatable/control.php @@ -172,6 +172,7 @@ class datatable extends control if($module == 'execution' && $method == 'bug') { unset($cols['execution']); + unset($cols['branch']); $execution = $this->loadModel('execution')->getByID($this->session->execution); $project = $this->loadModel('project')->getByID($execution->project); if(!$project->hasProduct && $project->model != 'scrum') unset($cols['plan']); diff --git a/module/datatable/lang/zh-cn.php b/module/datatable/lang/zh-cn.php index b48bdaf677..70857cb90f 100644 --- a/module/datatable/lang/zh-cn.php +++ b/module/datatable/lang/zh-cn.php @@ -14,7 +14,7 @@ $lang->datatable->required = '必选'; $lang->datatable->confirmReset = '恢复默认后,列表的相关设置将被清空,您确定要恢复默认吗?'; $lang->datatable->setGlobal = '应用到全局'; $lang->datatable->resetGlobal = '全局恢复默认'; -$lang->datatable->confirmSetGlobal = '应用到全局后,系统内非自定义的列表设置将默认按照改列表规则显示,您确定要应用到全局吗?'; +$lang->datatable->confirmSetGlobal = '应用到全局后,系统内非自定义的列表设置将默认按照该列表规则显示,您确定要应用到全局吗?'; $lang->datatable->confirmGlobalReset = '全局恢复默认后,系统内非自定义的列表设置将被清空,您确定要恢复默认吗?'; $lang->datatable->branch = '分支'; diff --git a/module/dataview/test/model/gettypeoptions.php b/module/dataview/test/model/gettypeoptions.php index fd952f2235..af24f061cb 100755 --- a/module/dataview/test/model/gettypeoptions.php +++ b/module/dataview/test/model/gettypeoptions.php @@ -11,7 +11,7 @@ cid=1 - 获取bug模块的字段列表。 - 第id条的name属性 @Bug编号 - - 第title条的name属性 @Bug名称 + - 第title条的name属性 @Bug标题 - 获取bug模块的字段列表。 - 第id条的name属性 @编号 - 第name条的name属性 @产品名称 @@ -20,5 +20,5 @@ cid=1 global $tester; $tester->loadModel('dataview'); -r($tester->dataview->getTypeOptions('bug')) && p('id:name;title:name') && e('Bug编号,Bug名称'); //获取bug模块的字段列表。 -r($tester->dataview->getTypeOptions('product')) && p('id:name;name:name') && e('编号,产品名称'); //获取bug模块的字段列表。 +r($tester->dataview->getTypeOptions('bug')) && p('id:name;title:name') && e('Bug编号,Bug标题'); //获取bug模块的字段列表。 +r($tester->dataview->getTypeOptions('product')) && p('id:name;name:name') && e('编号,产品名称'); //获取bug模块的字段列表。 diff --git a/module/entry/control.php b/module/entry/control.php index b41d16db5c..79885bd7ea 100644 --- a/module/entry/control.php +++ b/module/entry/control.php @@ -46,9 +46,9 @@ class entry extends control { if($_POST) { + if($this->post->freePasswd) unset($this->config->entry->form->create['account']); $entry = form::data($this->config->entry->form->create) ->setIF($this->post->allIP === 'on', 'ip', '*') - ->setIF($this->post->freePasswd == '1', 'account', '') ->add('createdBy', $this->app->user->account) ->add('createdDate', helper::now()) ->remove('allIP') @@ -79,9 +79,9 @@ class entry extends control { if($_POST) { + if($this->post->freePasswd) unset($this->config->entry->form->edit['account']); $entry = form::data($this->config->entry->form->edit) ->setIF($this->post->allIP === 'on', 'ip', '*') - ->setIF($this->post->freePasswd == '1', 'account', '') ->add('editedBy', $this->app->user->account) ->add('editedDate', helper::now()) ->remove('allIP') diff --git a/module/execution/config.php b/module/execution/config.php index 5a7c76ed67..a0542a065a 100644 --- a/module/execution/config.php +++ b/module/execution/config.php @@ -10,7 +10,7 @@ $config->execution->defaultBurnPeriod = 30; $config->execution->list = new stdclass(); $config->execution->list->exportFields = 'id,name,projectName,PM,begin,end,status,estimate,consumed,left,progress'; $config->execution->list->customCreateFields = ''; -$config->execution->list->customBatchEditFields = 'days,lifetime,teamName,desc,PO,QD,PM,RD'; +$config->execution->list->customBatchEditFields = 'days,lifetime,team,desc,PO,QD,PM,RD'; $config->execution->modelList['scrum'] = 'sprint'; $config->execution->modelList['waterfall'] = 'stage'; diff --git a/module/execution/config/dtable.php b/module/execution/config/dtable.php index 37e441b49c..59155a791b 100644 --- a/module/execution/config/dtable.php +++ b/module/execution/config/dtable.php @@ -196,6 +196,7 @@ $config->execution->importBug->dtable->fieldList['title']['title'] = $lang $config->execution->importBug->dtable->fieldList['title']['type'] = 'shortNestedTitle'; $config->execution->importBug->dtable->fieldList['title']['sortType'] = false; $config->execution->importBug->dtable->fieldList['title']['data-toggle'] = 'modal'; +$config->execution->importBug->dtable->fieldList['title']['data-size'] = 'lg'; $config->execution->importBug->dtable->fieldList['title']['link'] = array('module' => 'bug', 'method' => 'view', 'params' => 'bugID={id}'); $config->execution->importBug->dtable->fieldList['status']['title'] = $lang->bug->status; diff --git a/module/execution/config/form.php b/module/execution/config/form.php index 8cef71ae12..f5358d28fd 100644 --- a/module/execution/config/form.php +++ b/module/execution/config/form.php @@ -48,7 +48,7 @@ $config->execution->form->create['whitelist'] = array('type' => 'array', 'r $config->execution->form->create['openedVersion'] = array('type' => 'string', 'required' => false, 'control' => 'text', 'default' => $config->version); $config->execution->form->create['vision'] = array('type' => 'string', 'required' => false, 'control' => 'text', 'default' => $config->vision); -$config->execution->form->edit['project'] = array('type' => 'int', 'required' => true, 'control' => 'select', 'default' => 0); +$config->execution->form->edit['project'] = array('type' => 'int', 'required' => false, 'control' => 'select', 'default' => 0); $config->execution->form->edit['name'] = array('type' => 'string', 'required' => true, 'control' => 'text', 'default' => '', 'filter' => 'trim'); $config->execution->form->edit['code'] = array('type' => 'string', 'required' => false, 'control' => 'text', 'default' => '', 'filter' => 'trim'); $config->execution->form->edit['begin'] = array('type' => 'date', 'required' => true, 'control' => 'date', 'default' => null); diff --git a/module/execution/control.php b/module/execution/control.php index c0892db800..18bc3e748f 100644 --- a/module/execution/control.php +++ b/module/execution/control.php @@ -126,12 +126,12 @@ class execution extends control $this->session->set('taskList', $this->app->getURI(true), 'execution'); /* Set browse type. */ - $browseType = strtolower($status); - $execution = $this->commonAction($executionID, $status); + $browseType = strtolower($status); + $execution = $this->commonAction($executionID, $status); + $executionID = $execution->id; if($execution->type == 'kanban' && $this->config->vision != 'lite' && $this->app->getViewType() != 'json') $this->locate($this->createLink('execution', 'kanban', "executionID=$executionID")); /* Save the recently five executions visited in the cookie. */ - $executionID = $execution->id; $this->executionZen->setRecentExecutions($executionID); /* Append id for second sort and process the order by field. */ @@ -1273,7 +1273,10 @@ class execution extends control /* Set custom fields. */ foreach(explode(',', $this->config->execution->list->customBatchEditFields) as $field) { - $customFields[$field] = $field == 'lifetime' ? $this->lang->execution->type : str_replace($this->lang->executionCommon, $this->lang->execution->common, $this->lang->execution->$field); + $fieldTitle = $this->app->tab == 'execution' ? str_replace($this->lang->executionCommon, $this->lang->execution->common, $this->lang->execution->$field) : $this->lang->execution->$field; + if($field == 'lifetime') $fieldTitle = $this->app->tab == 'execution' ? $this->lang->execution->execType : $this->lang->execution->type; + if($field == 'team') $fieldTitle = $this->lang->execution->teamName; + $customFields[$field] = $fieldTitle; } $parentIdList = array(); @@ -2176,10 +2179,11 @@ class execution extends control * @param int $recPerPage * @param int $pageID * @param string $extra + * @param string $storyType * @access public * @return void */ - public function linkStory(int $objectID = 0, string $browseType = '', int $param = 0, string $orderBy = 'id_desc', int $recPerPage = 50, int $pageID = 1, string $extra = '') + public function linkStory(int $objectID = 0, string $browseType = '', int $param = 0, string $orderBy = 'id_desc', int $recPerPage = 50, int $pageID = 1, string $extra = '', string $storyType = 'story') { $this->loadModel('story'); $this->loadModel('product'); @@ -2202,9 +2206,8 @@ class execution extends control /* 通过objectID获取符合项目、执行、阶段和看板类型的对象。*/ $object = $this->project->getByID($objectID, 'project,sprint,stage,kanban'); - $browseLink = $this->session->executionStoryList; - if(empty($browseLink)) $browseLink = $this->createLink('execution', 'story', "executionID=$objectID"); - if($this->app->tab == 'project' and $object->multiple) $browseLink = $this->createLink('projectstory', 'story', "objectID=$objectID"); + $browseLink = $this->createLink('execution', 'story', "executionID=$objectID&storyType=$storyType"); + if($this->app->tab == 'project' and $object->multiple) $browseLink = $this->createLink('projectstory', 'story', "objectID=$objectID&productID=0&branch=0&browseType=¶m=0&storyType=$storyType"); if($object->type == 'kanban' && !$object->hasProduct) $this->lang->productCommon = $this->lang->project->common; @@ -2212,8 +2215,8 @@ class execution extends control if(!empty($_POST)) { - if($object->type != 'project' and $object->project != 0) $this->execution->linkStory($object->project, $this->post->stories ? $this->post->stories : array()); - $this->execution->linkStory($objectID, $this->post->stories ? $this->post->stories : array(), $extra); + if($object->type != 'project' and $object->project != 0) $this->execution->linkStory($object->project, $this->post->stories ? $this->post->stories : array(), '', array(), $storyType); + $this->execution->linkStory($objectID, $this->post->stories ? $this->post->stories : array(), $extra, array(), $storyType); if(isInModal()) return $this->sendSuccess(array('closeModal' => true, 'load' => true, 'callback' => "refreshKanban()")); @@ -2253,15 +2256,25 @@ class execution extends control } } + if($storyType == 'requirement') + { + $this->app->loadLang('projectstory'); + $this->lang->story->title = str_replace($this->lang->SRCommon, $this->lang->URCommon, $this->lang->story->title); + $this->lang->projectstory->whyNoStories = str_replace($this->lang->SRCommon, $this->lang->URCommon, $this->lang->projectstory->whyNoStories); + $this->lang->execution->linkStory = str_replace($this->lang->SRCommon, $this->lang->URCommon, $this->lang->story->linkStory); + if(isset($this->config->product->search['fields']['stage'])) unset($this->config->product->search['fields']['stage']); + } + /* Build the search form. */ - $actionURL = $this->createLink($this->app->rawModule, 'linkStory', "objectID=$objectID&browseType=bySearch&queryID=myQueryID"); + $actionURL = $this->createLink($this->app->rawModule, 'linkStory', "objectID=$objectID&browseType=bySearch&queryID=myQueryID&orderBy=$orderBy&recPerPage=$recPerPage&pageID=$pageID&storyType=$storyType"); $branchGroups = $this->loadModel('branch')->getByProducts(array_keys($products)); $queryID = ($browseType == 'bySearch') ? (int)$param : 0; $this->execution->buildStorySearchForm($products, $branchGroups, $modules, $queryID, $actionURL, 'linkStory', $object); - if($browseType == 'bySearch') $allStories = $this->story->getBySearch(implode(',', array_keys($products)), '', $queryID, $orderBy, $objectID); - if($browseType != 'bySearch') $allStories = $this->story->getProductStories(implode(',', array_keys($products)), $branchIDList, '0', 'active', 'story', $orderBy, false, '', null); - $linkedStories = $this->story->getExecutionStoryPairs($objectID); + if($browseType == 'bySearch') $allStories = $this->story->getBySearch(implode(',', array_keys($products)), '', $queryID, $orderBy, $objectID, $storyType); + if($browseType != 'bySearch') $allStories = $this->story->getProductStories(implode(',', array_keys($products)), $branchIDList, '0', 'active', $storyType, $orderBy, false, '', null); + $linkedStories = $this->story->getExecutionStoryPairs($objectID, 0, 'all', 0, 'full', 'all', $storyType); + foreach($allStories as $id => $story) { if(isset($linkedStories[$story->id])) unset($allStories[$id]); @@ -2308,6 +2321,8 @@ class execution extends control $this->view->project = $project; $this->view->executionID = $object->id; $this->view->projectID = $object->id; + $this->view->storyType = $storyType; + if($this->config->edition == 'ipd') $this->view->roadmaps = $this->loadModel('roadmap')->getPairs($productPairs); $this->display(); } diff --git a/module/execution/model.php b/module/execution/model.php index 6f18725f0e..1cb0946e3e 100755 --- a/module/execution/model.php +++ b/module/execution/model.php @@ -2791,10 +2791,11 @@ class executionModel extends model * @param array $stories * @param string $extra * @param array $lanes + * @param string $storyType * @access public * @return bool */ - public function linkStory(int $executionID, array $stories = array(), string $extra = '', array $lanes = array()): bool + public function linkStory(int $executionID, array $stories = array(), string $extra = '', array $lanes = array(), string $storyType = 'story'): bool { if(empty($executionID) || empty($stories)) return false; @@ -2840,6 +2841,7 @@ class executionModel extends model $action = $execution->type == 'project' ? 'linked2project' : 'linked2execution'; if($action == 'linked2execution' and $execution->type == 'kanban') $action = 'linked2kanban'; if($execution->multiple or $execution->type == 'project') $this->action->create('story', $storyID, $action, '', $executionID); + if($storyType == 'requirement' and $execution->model == 'ipd') $this->dao->update(TABLE_STORY)->set('status')->eq('developing')->where('id')->eq($storyID)->exec(); } if(!isset($output['laneID']) or !isset($output['columnID'])) $this->kanban->updateLane($executionID, 'story'); @@ -2936,6 +2938,7 @@ class executionModel extends model public function unlinkStory(int $executionID, int $storyID, int $laneID = 0, int $columnID = 0): array|bool { $execution = $this->dao->findById($executionID)->from(TABLE_EXECUTION)->fetch(); + $storyType = $this->dao->findById($storyID)->from(TABLE_STORY)->fetch('type'); if($execution->type == 'project') { $executions = $this->dao->select('*')->from(TABLE_EXECUTION)->where('parent')->eq($executionID)->fetchAll('id'); @@ -2944,6 +2947,9 @@ class executionModel extends model } $this->dao->delete()->from(TABLE_PROJECTSTORY)->where('project')->eq($executionID)->andWhere('story')->eq($storyID)->limit(1)->exec(); + /* In ipd project, unlink stories change it's status to launched. */ + if($execution->model == 'ipd' and $storyType == 'requirement') $this->dao->update(TABLE_STORY)->set('status')->eq('launched')->where('id')->eq($storyID)->exec(); + /* Resolve TABLE_KANBANCELL's field cards. */ if($execution->type == 'kanban') { diff --git a/module/execution/ui/batchedit.html.php b/module/execution/ui/batchedit.html.php index 60db5c7559..eb2b808f79 100644 --- a/module/execution/ui/batchedit.html.php +++ b/module/execution/ui/batchedit.html.php @@ -56,7 +56,7 @@ formBatchPanel formBatchItem ( set::name('name'), - set::label($lang->execution->name), + set::label($app->tab == 'execution' ? $lang->execution->execName : $lang->execution->name), set::width('240px'), set::required(true) ), @@ -72,14 +72,14 @@ formBatchPanel $setCode ? formBatchItem ( set::name('code'), - set::label($lang->execution->code), + set::label($app->tab == 'execution' ? $lang->execution->execCode : $lang->execution->code), set::width('136px'), set::required(strpos(",{$config->execution->edit->requiredFields},", ',code,') !== false) ) : null, formBatchItem ( set::name('PM'), - set::label($lang->execution->PM), + set::label($app->tab == 'execution' ? $lang->execution->execPM : $lang->execution->PM), set::control('picker'), set::ditto(true), set::defaultDitto('off'), @@ -123,9 +123,7 @@ formBatchPanel formBatchItem ( set::name('lifetime'), - set::label( - $lang->execution->type - ), + set::label($app->tab == 'execution' ? $lang->execution->execType : $lang->execution->type), set::control('picker'), set::items($lang->execution->lifeTimeList), set::width('120px'), @@ -171,7 +169,7 @@ formBatchPanel formBatchItem ( set::name('desc'), - set::label($lang->execution->desc), + set::label($app->tab == 'execution' ? $lang->execution->execDesc : $lang->execution->desc), set::control('textarea'), set::width('160px'), set::hidden(strpos("{$showFields}", 'desc') === false) diff --git a/module/execution/ui/bug.html.php b/module/execution/ui/bug.html.php index e641a3e746..647169dac8 100644 --- a/module/execution/ui/bug.html.php +++ b/module/execution/ui/bug.html.php @@ -92,6 +92,8 @@ dtable set::userMap($users), set::cols($cols), set::data(array_values($bugs)), + set::priList($lang->bug->priList), + set::severityList($lang->bug->severityList), set::checkable($canBatchAssignTo), set::orderBy($orderBy), set::sortLink(createLink('execution', 'bug', "executionID={$execution->id}&productID={$productID}&branch={$branchID}&orderBy={name}_{sortType}&build=$buildID&type=$type¶m=$param&recTotal={$pager->recTotal}&recPerPage={$pager->recPerPage}&pageID={$pager->pageID}")), diff --git a/module/execution/ui/grouptask.html.php b/module/execution/ui/grouptask.html.php index 4b1d1a1dba..a3eea62d6f 100644 --- a/module/execution/ui/grouptask.html.php +++ b/module/execution/ui/grouptask.html.php @@ -73,7 +73,7 @@ $canImportTask = hasPriv('task', 'importTask'); $canImportBug = hasPriv('task', 'importBug'); if(common::canModify('execution', $execution)) { - $createLink = $this->createLink('task', 'create', "executionID={$execution->id}" . ($app->tab == 'project' ? '#app=project' : '')); + $createLink = $this->createLink('task', 'create', "executionID={$execution->id}") . ($app->tab == 'project' ? '#app=project' : ''); if(commonModel::isTutorialMode()) { $wizardParams = helper::safe64Encode("executionID={$execution->id}"); @@ -511,7 +511,7 @@ else ( set::text($lang->task->create), set::icon('plus'), - set::url(createLink('task', 'create', "execution={$executionID}" . (isset($moduleID) ? "&storyID=&moduleID={$moduleID}" : '' . ($app->tab == 'project' ? '#app=project' : '')))), + set::url(createLink('task', 'create', "execution={$executionID}" . (isset($moduleID) ? "&storyID=&moduleID={$moduleID}" : '')) . ($app->tab == 'project' ? '#app=project' : '')), setClass('btn primary-pale border-primary ml-2') ) : null ) diff --git a/module/execution/ui/kanban.html.php b/module/execution/ui/kanban.html.php index 7f8b7900d8..d3cae73e10 100644 --- a/module/execution/ui/kanban.html.php +++ b/module/execution/ui/kanban.html.php @@ -91,7 +91,7 @@ if($canBatchCreateBug) if(($hasStoryButton or $hasBugButton) and $hasTaskButton) $createMenu[] = array('type' => 'divider'); if($canCreateTask) $createMenu[] = array('text' => $lang->task->create, 'url' => helper::createLink('task', 'create', "execution=$execution->id"), 'data-toggle' => 'modal', 'data-size' => 'lg'); if($canImportBug) $createMenu[] = array('text' => $lang->execution->importBug, 'url' => helper::createLink('execution', 'importBug', "execution=$execution->id"), 'data-toggle' => 'modal'); -if($canImportTask) $createMenu[] = array('text' => $lang->execution->importTask, 'url' => helper::createLink('execution', 'importTask', "execution=$execution->id"), 'data-toggle' => 'modal'); +if($canImportTask) $createMenu[] = array('text' => $lang->execution->importTask, 'url' => helper::createLink('execution', 'importTask', "execution=$execution->id")); if($canBatchCreateTask) $createMenu[] = array('text' => $lang->execution->batchCreateTask, 'url' => helper::createLink('task', 'batchCreate', "execution=$execution->id"), 'data-toggle' => 'modal', 'data-size' => 'lg'); jsVar('laneCount', $laneCount); diff --git a/module/execution/ui/linkstory.html.php b/module/execution/ui/linkstory.html.php index d69e3e6248..26e3d9f3c6 100644 --- a/module/execution/ui/linkstory.html.php +++ b/module/execution/ui/linkstory.html.php @@ -12,6 +12,8 @@ declare(strict_types=1); namespace zin; +data('activeMenuID', $storyType); + featureBar(li ( setClass('nav-item'), @@ -29,6 +31,8 @@ searchForm $cols = $config->execution->linkStory->dtable->fieldList; $cols['module']['map'] = $modules; $cols['product']['map'] = $productPairs; + +if($storyType == 'requirement') $cols['title']['title'] = str_replace($lang->SRCommon, $lang->URCommon, $lang->story->title); if($productType != 'normal') { $cols['branch']['title'] = $lang->product->branchName[$productType]; @@ -44,7 +48,7 @@ jsVar('branchGroups', $branchGroups); $footToolbar['items'][] = array( 'text' => $lang->save, 'className' => 'btn secondary batch-btn ajax-btn import-story-btn size-sm', - 'data-url' => createLink('execution', 'linkStory', "objectID=$object->id") + 'data-url' => createLink('execution', 'linkStory', "projectID=$object->id&browseType={$browseType}¶m={$param}&orderBy={$orderBy}&recPerPage={$pager->recPerPage}&pageID={$pager->pageID}&extra=&storyType=$storyType") ); if(!isInModal()) { diff --git a/module/execution/ui/story.html.php b/module/execution/ui/story.html.php index eff80fceba..97e961ed3b 100644 --- a/module/execution/ui/story.html.php +++ b/module/execution/ui/story.html.php @@ -79,8 +79,8 @@ if(commonModel::isTutorialMode()) $createItem = array('text' => $lang->story->create, 'url' => $createLink); $batchCreateItem = array('text' => $lang->story->batchCreate, 'url' => $batchCreateLink); -$canLinkStory = $execution->hasProduct && $canModifyProduct && hasPriv('execution', 'linkStory'); -$canlinkPlanStory = $execution->hasProduct && $canModifyProduct && hasPriv('execution', 'importPlanStories'); +$canLinkStory = ($execution->hasProduct || $app->tab == 'execution') && $canModifyProduct && hasPriv('execution', 'linkStory'); +$canlinkPlanStory = ($execution->hasProduct || $app->tab == 'execution') && $canModifyProduct && hasPriv('execution', 'importPlanStories'); $linkStoryUrl = createLink('execution', 'linkStory', "project={$execution->id}"); if(commonModel::isTutorialMode()) @@ -95,6 +95,22 @@ $linkPlanItem = array('text' => $lang->execution->linkStoryByPlan, 'url' => '#li $product ? toolbar ( + common::hasPriv('execution', 'storykanban') && $storyType == 'story' ? btnGroup + ( + btn + ( + setClass('text-primary font-bold shadow-inner bg-canvas'), + set::icon('format-list-bulleted'), + set::hint($lang->execution->list), + set::url(inlink('story', "executionID={$execution->id}&storyType={$storyType}&orderBy={$orderBy}&type=all")) + ), + btn + ( + set::icon('kanban'), + set::hint($lang->execution->kanban), + set::url($this->createLink('execution', 'storykanban', "executionID={$execution->id}")) + ), + ) : null, hasPriv('story', 'report') ? item(set(array ( 'text' => $lang->story->report->common, @@ -381,6 +397,7 @@ $options = array('storyTasks' => $storyTasks, 'storyBugs' => $storyBugs, 'storyC foreach($stories as $story) { $story->moduleID = $story->module; + $story->from = 'execution'; $data[] = $this->story->formatStoryForList($story, $options, $storyType); if(!isset($story->children)) continue; diff --git a/module/execution/ui/taskkanban.html.php b/module/execution/ui/taskkanban.html.php index 939913b7c0..ef379715a1 100644 --- a/module/execution/ui/taskkanban.html.php +++ b/module/execution/ui/taskkanban.html.php @@ -183,7 +183,7 @@ row btn(set::type('link'), set::icon('import'), $lang->import), set::items(array ( - common::hasPriv('execution', 'importTask') ? array('text' => $lang->execution->importTask, 'url' => createLink('execution', 'importTask', "execution=$execution->id"), 'data-toggle' => 'modal', 'data-size' => 'lg') : null, + common::hasPriv('execution', 'importTask') && $execution->multiple ? array('text' => $lang->execution->importTask, 'url' => createLink('execution', 'importTask', "execution=$execution->id")) : null, ($features['qa'] && common::hasPriv('execution', 'importBug')) ? array('text' => $lang->execution->importBug, 'url' => createLink('execution', 'importBug', "execution=$execution->id"), 'data-toggle' => 'modal', 'data-size' => 'lg') : null )) ) : null, diff --git a/module/execution/ui/tree.html.php b/module/execution/ui/tree.html.php index ef91286229..6d9ebc809b 100644 --- a/module/execution/ui/tree.html.php +++ b/module/execution/ui/tree.html.php @@ -77,7 +77,7 @@ toolbar 'icon' => 'plus', 'text' => $lang->task->create, 'class' => 'primary create-execution-btn', - 'url' => createLink('task', 'create', "execution={$executionID}" . ($app->tab == 'project' ? '#app=project' : '')) + 'url' => createLink('task', 'create', "execution={$executionID}") . ($app->tab == 'project' ? '#app=project' : '') ))) : null ); @@ -97,7 +97,7 @@ if(empty($tree)) ( set::text($lang->task->create), set::icon('plus'), - set::url(createLink('task', 'create', "execution={$executionID}" . (isset($moduleID) ? "&storyID=&moduleID={$moduleID}" : '' . ($app->tab == 'project' ? '#app=project' : '')))), + set::url(createLink('task', 'create', "execution={$executionID}" . (isset($moduleID) ? "&storyID=&moduleID={$moduleID}" : '')) . ($app->tab == 'project' ? '#app=project' : '')), setClass('btn primary-pale border-primary ml-2') ) : null ); diff --git a/module/execution/view/storykanban.html.php b/module/execution/view/storykanban.html.php index 8e4190ab52..5e2591bb25 100644 --- a/module/execution/view/storykanban.html.php +++ b/module/execution/view/storykanban.html.php @@ -35,9 +35,11 @@ $canLinkStory = $execution->hasProduct or $execution->multiple; createLink('execution', 'storykanban', "executionID=$execution->id"), "  ", '', "class='btn btn-icon text-primary switchBtn' title='{$lang->execution->kanban}' data-type='bykanban'");?> + loadModel('story')->checkForceReview()) common::printLink('story', 'create', "productID=$productID&branch=&moduleID=0&story=0&execution=$execution->id", " " . $lang->execution->createStory, '', "class='btn btn-secondary' class='btn btn-link export' data-group='execution'");?> config->bug->search['params']['product']['values'] = array(''=>''); } - $this->config->bug->search['params']['execution']['values'] = array(''=>'') + $executions + array('all'=>$this->lang->execution->aboveAllExecution); + + if(!$execution->multiple) + { + unset($this->config->bug->search['fields']['execution'], $this->config->bug->search['params']['execution']); + } + else + { + $this->config->bug->search['params']['execution']['values'] = array(''=>'') + $executions + array('all'=>$this->lang->execution->aboveAllExecution); + } + $this->config->bug->search['params']['plan']['values'] = $this->loadModel('productplan')->getPairs(array_keys($products)); $this->config->bug->search['module'] = 'importBug'; $this->config->bug->search['params']['confirmed']['values'] = $this->lang->bug->confirmedList; @@ -1995,8 +2004,9 @@ class executionZen extends execution array_map(function($executionProduct) use(&$multiBranchProduct){if($executionProduct->type != 'normal') $multiBranchProduct = true;}, $executionProductList); $importPlanStoryTips = $multiBranchProduct ? $this->lang->execution->importBranchPlanStory : $this->lang->execution->importPlanStory; - $confirmURL = inlink('create', "projectID=$projectID&executionID=$executionID©ExecutionID=&planID=$planID&confirm=yes"); - $cancelURL = inlink('create', "projectID=$projectID&executionID=$executionID"); + if($execution->type == 'stage') $importPlanStoryTips = str_replace($this->lang->executionCommon, $this->lang->execution->stage, $importPlanStoryTips); + $confirmURL = inlink('create', "projectID=$projectID&executionID=$executionID©ExecutionID=&planID=$planID&confirm=yes"); + $cancelURL = inlink('create', "projectID=$projectID&executionID=$executionID"); return $this->send(array('result' => 'success', 'load' => array('confirm' => $importPlanStoryTips, 'confirmed' => $confirmURL, 'canceled' => $cancelURL))); } } diff --git a/module/gitlab/model.php b/module/gitlab/model.php index c831135ce2..3d1f6aefbf 100644 --- a/module/gitlab/model.php +++ b/module/gitlab/model.php @@ -597,8 +597,8 @@ class gitlabModel extends model $result = commonModel::http($url . "&per_page={$pager->recPerPage}&order_by={$order[0]}&sort={$order[1]}&page={$pager->pageID}&search={$keyword}&search_namespaces=true", null, array(), array(), 'data', 'GET', 30, true, false); $header = $result['header']; - $recTotal = $header['X-Total']; - $recPerPage = $header['X-Per-Page']; + $recTotal = zget($header, 'X-Total', $header['x-total']); + $recPerPage = zget($header, 'X-Per-Page', $header['x-per-page']); $this->app->loadClass('pager', true); $pager = pager::init($recTotal, $recPerPage, $pager->pageID); diff --git a/module/gitlab/test/model/webhookassignissue.php b/module/gitlab/test/model/webhookassignissue.php index 1e618a56b6..3bbb948d66 100755 --- a/module/gitlab/test/model/webhookassignissue.php +++ b/module/gitlab/test/model/webhookassignissue.php @@ -19,6 +19,7 @@ cid=1 include dirname(__FILE__, 5) . '/test/lib/init.php'; include dirname(__FILE__, 2) . '/gitlab.class.php'; +zdTable('product')->gen(20); zdTable('story')->gen(5); su('admin'); @@ -45,4 +46,4 @@ $issue->objectID = 1; r($gitlab->webhookAssignIssueTest($issue)) && p('assignedTo') && e('~~'); // 对象ID正确 $issue->object->assignedTo = 'user1'; -r($gitlab->webhookAssignIssueTest($issue)) && p('assignedTo') && e('user1'); // 对象ID正确,更新指派给 \ No newline at end of file +r($gitlab->webhookAssignIssueTest($issue)) && p('assignedTo') && e('user1'); // 对象ID正确,更新指派给 diff --git a/module/gitlab/test/model/webhookcloseissue.php b/module/gitlab/test/model/webhookcloseissue.php index 4e533d8ccd..f9417ed8cc 100755 --- a/module/gitlab/test/model/webhookcloseissue.php +++ b/module/gitlab/test/model/webhookcloseissue.php @@ -19,6 +19,7 @@ cid=1 include dirname(__FILE__, 5) . '/test/lib/init.php'; include dirname(__FILE__, 2) . '/gitlab.class.php'; +zdTable('product')->gen(20); zdTable('story')->gen(5); su('admin'); @@ -45,4 +46,4 @@ $issue->objectID = 1; r($gitlab->webhookCloseIssueTest($issue)) && p('lastEditedBy') && e('~~'); // 对象ID正确 $issue->object->lastEditedBy = 'user1'; -r($gitlab->webhookCloseIssueTest($issue)) && p('lastEditedBy') && e('user1'); // 对象ID正确,更新最后编辑人 \ No newline at end of file +r($gitlab->webhookCloseIssueTest($issue)) && p('lastEditedBy') && e('user1'); // 对象ID正确,更新最后编辑人 diff --git a/module/index/config.php b/module/index/config.php index 91d3379ab0..1de47bcad0 100644 --- a/module/index/config.php +++ b/module/index/config.php @@ -36,3 +36,4 @@ $config->index->oldPages[] = 'api-debug'; $config->index->oldPages[] = 'misc-downloadclient'; $config->index->oldPages[] = 'datatable-ajaxoldcustom'; $config->index->oldPages[] = 'weekly-index'; +$config->index->oldPages[] = 'execution-storykanban'; diff --git a/module/index/css/index.ui.css b/module/index/css/index.ui.css index 87df6bc507..5a44039355 100644 --- a/module/index/css/index.ui.css +++ b/module/index/css/index.ui.css @@ -62,3 +62,9 @@ } [lang^='en'] {--zt-menu-width: 106px;} + + #visionTips {width: 280px; height: 0; position: absolute; bottom: 120px; left: 0; z-index: 200; color: #ffffff;} + #visionTips .inner {width: 270px; height: 58px; position: relative; bottom: 0; left: 0; border-radius: 8px; display: flex; justify-content: space-around; align-items: center;} + #visionTips .line {width: 2px; height: 31px; position: absolute; bottom: -31px; left: 64px;} + #visionTips .circle {width: 10px; height: 10px; border-radius: 50%; border-width: 3px; border-style: solid; position: relative; left: -4px; top: 31px; background-color: white;color: #fff; border-color: var(--color-primary-500);} + #visionTips .inner > button {border-color: white; background: var(--skin-text);} diff --git a/module/index/ui/index.html.php b/module/index/ui/index.html.php index 2648a90b03..58eec51e00 100644 --- a/module/index/ui/index.html.php +++ b/module/index/ui/index.html.php @@ -92,7 +92,23 @@ div(setID('apps')); div ( setID('appsBar'), - div(setID('visionSwitcher'), visionSwitcher()), + div( + setID('visionSwitcher'), + visionSwitcher(), + (count(explode(',', $this->app->user->visions)) > 1 && empty($config->global->hideVisionTips)) ? + div( + setID('visionTips'), + div( + setClass('inner bg-primary'), + span($lang->visionTips), + button(setClass('btn btn-primary'), on::click('hideVisionTips'), $lang->IKnow), + div( + setClass('line bg-primary pannel-primary'), + div(setClass('circle alert-primary-inverse')) + ) + ), + ) : null, + ), ul ( setID('appTabs'), diff --git a/module/instance/test/model/countolddomain.php b/module/instance/test/model/countolddomain.php index 9a87b8a745..6a4b502be1 100755 --- a/module/instance/test/model/countolddomain.php +++ b/module/instance/test/model/countolddomain.php @@ -1,6 +1,8 @@ #!/usr/bin/env php gen(0); zdTable('instance')->gen(5); zdTable('space')->gen(5); @@ -18,4 +20,4 @@ global $tester; $tester->loadModel('instance'); $oldDomain = $tester->instance->countOldDomain(); -r($oldDomain) && p('') && e('5'); // 查看获取到的所有服务的数量 \ No newline at end of file +r($oldDomain) && p('') && e('5'); // 查看获取到的所有服务的数量 diff --git a/module/instance/test/model/getservicecount.php b/module/instance/test/model/getservicecount.php index 1ed0d39b63..ea6e8ddc37 100755 --- a/module/instance/test/model/getservicecount.php +++ b/module/instance/test/model/getservicecount.php @@ -1,9 +1,6 @@ #!/usr/bin/env php gen(5); -zdTable('space')->gen(5); - /** title=instanceModel->getServiceCount(); @@ -15,7 +12,11 @@ cid=1 */ -global $tester; +zdTable('user')->gen(5); +zdTable('instance')->gen(5); +zdTable('space')->gen(5); + +global $tester, $app; $tester->loadModel('instance'); su('admin'); @@ -24,4 +25,4 @@ r($instance) && p('') && e('1'); // 查看获取到的所有服务的数量 su('user1'); $instance = $tester->instance->getServiceCount(); -r($instance) && p('') && e('1'); // 查看获取到的所有服务的数量 \ No newline at end of file +r($instance) && p('') && e('1'); // 查看获取到的所有服务的数量 diff --git a/module/kanban/config/form.php b/module/kanban/config/form.php index b4f7a9616f..c9e1a9ad8d 100644 --- a/module/kanban/config/form.php +++ b/module/kanban/config/form.php @@ -25,22 +25,24 @@ $config->kanban->form->closeSpace['closedDate'] = array('type' => 'datetime' $config->kanban->form->closeSpace['lastEditedBy'] = array('type' => 'string', 'required' => true, 'default' => $app->user->account); $config->kanban->form->closeSpace['lastEditedDate'] = array('type' => 'datetime', 'required' => true, 'default' => $now); -$config->kanban->form->create['name'] = array('type' => 'string', 'required' => true, 'default' => ''); -$config->kanban->form->create['showWIP'] = array('type' => 'string', 'required' => true, 'default' => ''); -$config->kanban->form->create['space'] = array('type' => 'int', 'required' => true, 'default' => ''); -$config->kanban->form->create['owner'] = array('type' => 'string', 'required' => false, 'default' => ''); -$config->kanban->form->create['team'] = array('type' => 'array', 'required' => false, 'default' => '', 'filter' => 'join'); -$config->kanban->form->create['fluidBoard'] = array('type' => 'string', 'required' => false, 'default' => ''); -$config->kanban->form->create['colWidth'] = array('type' => 'int', 'required' => false, 'default' => ''); -$config->kanban->form->create['minColWidth'] = array('type' => 'int', 'required' => false, 'default' => ''); -$config->kanban->form->create['maxColWidth'] = array('type' => 'int', 'required' => false, 'default' => ''); -$config->kanban->form->create['archived'] = array('type' => 'string', 'required' => false, 'default' => ''); -$config->kanban->form->create['performable'] = array('type' => 'string', 'required' => false, 'default' => ''); -$config->kanban->form->create['alignment'] = array('type' => 'string', 'required' => false, 'default' => ''); -$config->kanban->form->create['desc'] = array('type' => 'string', 'required' => false, 'default' => '', 'control' => 'editor'); -$config->kanban->form->create['whitelist'] = array('type' => 'array', 'required' => false, 'default' => '', 'filter' => 'join'); -$config->kanban->form->create['createdBy'] = array('type' => 'string', 'required' => true, 'default' => $app->user->account); -$config->kanban->form->create['createdDate'] = array('type' => 'datetime', 'required' => false, 'default' => $now); +$config->kanban->form->create['name'] = array('type' => 'string', 'required' => true, 'default' => ''); +$config->kanban->form->create['showWIP'] = array('type' => 'string', 'required' => false, 'default' => ''); +$config->kanban->form->create['space'] = array('type' => 'int', 'required' => true, 'default' => ''); +$config->kanban->form->create['owner'] = array('type' => 'string', 'required' => false, 'default' => ''); +$config->kanban->form->create['team'] = array('type' => 'array', 'required' => false, 'default' => '', 'filter' => 'join'); +$config->kanban->form->create['fluidBoard'] = array('type' => 'string', 'required' => false, 'default' => ''); +$config->kanban->form->create['colWidth'] = array('type' => 'int', 'required' => false, 'default' => ''); +$config->kanban->form->create['minColWidth'] = array('type' => 'int', 'required' => false, 'default' => ''); +$config->kanban->form->create['maxColWidth'] = array('type' => 'int', 'required' => false, 'default' => ''); +$config->kanban->form->create['archived'] = array('type' => 'string', 'required' => false, 'default' => ''); +$config->kanban->form->create['performable'] = array('type' => 'string', 'required' => false, 'default' => ''); +$config->kanban->form->create['alignment'] = array('type' => 'string', 'required' => false, 'default' => ''); +$config->kanban->form->create['desc'] = array('type' => 'string', 'required' => false, 'default' => '', 'control' => 'editor'); +$config->kanban->form->create['whitelist'] = array('type' => 'array', 'required' => false, 'default' => '', 'filter' => 'join'); +$config->kanban->form->create['createdBy'] = array('type' => 'string', 'required' => true, 'default' => $app->user->account); +$config->kanban->form->create['createdDate'] = array('type' => 'datetime', 'required' => false, 'default' => $now); +$config->kanban->form->create['copyKanbanID'] = array('type' => 'int', 'required' => false, 'default' => 0); +$config->kanban->form->create['copyRegion'] = array('type' => 'string', 'required' => false, 'default' => '0'); $config->kanban->form->edit['name'] = array('type' => 'string', 'required' => true, 'default' => ''); $config->kanban->form->edit['space'] = array('type' => 'int', 'required' => true, 'default' => ''); diff --git a/module/kanban/js/create.ui.js b/module/kanban/js/create.ui.js index 060e911413..514c80f07c 100644 --- a/module/kanban/js/create.ui.js +++ b/module/kanban/js/create.ui.js @@ -6,7 +6,8 @@ window.clickCopyCard = function(event) window.setCopyKanban = function(kanbanID) { - const url = $.createLink('kanban', 'create', 'spaceID=' + spaceID + '&type=' + spaceType + '©KanbanID=' + kanbanID); + copyRegion = $('[name=copyRegionInfo]').prop('checked'); + const url = $.createLink('kanban', 'create', 'spaceID=' + spaceID + '&type=' + spaceType + '©KanbanID=' + kanbanID + '&exyra=copyRegion=' + (copyRegion ? '1' : '0')); loadPartial(url, '#WIPCountBox, #spaceBox, #nameBox, #ownerBox, #teamBox, #fixedColBox, #autoColBox, #archiveBox, #manageProgressBox, #alignmentBox, #descBox, #whitelistBox'); } diff --git a/module/kanban/model.php b/module/kanban/model.php index bfc176df8c..dcca6b80a8 100755 --- a/module/kanban/model.php +++ b/module/kanban/model.php @@ -2133,20 +2133,20 @@ class kanbanModel extends model if(dao::isError()) return false; - $kanbanID = $this->dao->lastInsertID(); - $kanban = $this->getByID($kanbanID); + $kanbanID = $this->dao->lastInsertID(); + $kanbanInfo = $this->getByID($kanbanID); $this->loadModel('action')->create('kanban', $kanbanID, 'created'); $this->loadModel('file')->saveUpload('kanban', $kanbanID); $this->file->updateObjectID($this->post->uid, $kanbanID, 'kanban'); - $this->post->copyRegion ? $this->copyRegions($kanban, $this->post->copyKanbanID): $this->createDefaultRegion($kanban); + $kanban->copyRegion ? $this->copyRegions($kanbanInfo, $kanban->copyKanbanID): $this->createDefaultRegion($kanbanInfo); - if(!empty($kanban->team) || !empty($kanban->whitelist)) + if(!empty($kanbanInfo->team) || !empty($kanbanInfo->whitelist)) { - $type = !empty($kanban->team) ? 'team' : 'whitelist'; - $kanbanMembers = empty($kanban->{$type}) ? array() : explode(',', $kanban->{$type}); - $this->addSpaceMembers($kanban->space, $type, $kanbanMembers); + $type = !empty($kanbanInfo->team) ? 'team' : 'whitelist'; + $kanbanMembers = empty($kanbanInfo->{$type}) ? array() : explode(',', $kanbanInfo->{$type}); + $this->addSpaceMembers($kanbanInfo->space, $type, $kanbanMembers); } return $kanbanID; @@ -3103,7 +3103,7 @@ class kanbanModel extends model $parent = $this->getColumnByID($column->parent); /* If the parent column is normal now, put its card into child column. */ - if($parent->parent != -1) + if($parent && $parent->parent != -1) { $parentCells = $this->dao->select('*')->from(TABLE_KANBANCELL) ->where('`column`')->eq($column->parent) diff --git a/module/kanban/tao.php b/module/kanban/tao.php index b282a8e439..acadced9b5 100644 --- a/module/kanban/tao.php +++ b/module/kanban/tao.php @@ -22,7 +22,7 @@ class kanbanTao extends kanbanModel */ protected function createKanban($kanban) { - $this->dao->insert(TABLE_KANBAN)->data($kanban) + $this->dao->insert(TABLE_KANBAN)->data($kanban, 'copyRegion,copyKanbanID') ->autoCheck() ->batchCheck($this->config->kanban->create->requiredFields, 'notempty') ->checkIF(!$kanban->fluidBoard, 'colWidth', 'ge', $this->config->minColWidth) diff --git a/module/kanban/test/model/getpagetoolbar.php b/module/kanban/test/model/getpagetoolbar.php index e96ba15775..30c833f06c 100755 --- a/module/kanban/test/model/getpagetoolbar.php +++ b/module/kanban/test/model/getpagetoolbar.php @@ -11,7 +11,7 @@ timeout=0 cid=1 - 查看普通用户获取操作按钮的长度 @161 -- 查看管理员获取操作按钮的长度 @1243 +- 查看管理员获取操作按钮的长度 @1282 */ global $tester; @@ -24,5 +24,5 @@ $toolbar1 = $tester->kanban->getPageToolBar($kanban1); su('admin'); $toolbar2 = $tester->kanban->getPageToolBar($kanban2); -r(strlen($toolbar1)) && p('') && e('161'); // 查看普通用户获取操作按钮的长度 -r(strlen($toolbar2)) && p('') && e('1243'); // 查看管理员获取操作按钮的长度 \ No newline at end of file +r(strlen($toolbar1)) && p('') && e('161'); // 查看普通用户获取操作按钮的字符长度 +r(strlen($toolbar2)) && p('') && e('1282'); // 查看管理员获取操作按钮的字符长度 diff --git a/module/kanban/test/yaml/kanbanregion.yaml b/module/kanban/test/yaml/kanbanregion.yaml index 19f4404425..37f163294c 100644 --- a/module/kanban/test/yaml/kanbanregion.yaml +++ b/module/kanban/test/yaml/kanbanregion.yaml @@ -17,10 +17,8 @@ fields: - field: createdDate range: "20201101 000000:1D" type: timestamp - format: "YY/MM/DD hh::mm:ss" - postfix: "\t" + format: "YYYY-MM-DD hh::mm:ss" - field: lastEditedDate range: "20220101 000000:1D" type: timestamp - format: "YY/MM/DD" - postfix: "\t" + format: "YYYY-MM-DD" diff --git a/module/kanban/test/yaml/rdkanbanregion.yaml b/module/kanban/test/yaml/rdkanbanregion.yaml index f266d6dc71..2cc10f4df4 100644 --- a/module/kanban/test/yaml/rdkanbanregion.yaml +++ b/module/kanban/test/yaml/rdkanbanregion.yaml @@ -17,10 +17,8 @@ fields: - field: createdDate range: "20201101 000000:1D" type: timestamp - format: "YY/MM/DD hh::mm:ss" - postfix: "\t" + format: "YYYY-MM-DD hh::mm:ss" - field: lastEditedDate range: "20220101 000000:1D" type: timestamp - format: "YY/MM/DD" - postfix: "\t" + format: "YYYY-MM-DD" diff --git a/module/kanban/ui/create.html.php b/module/kanban/ui/create.html.php index 293ed65593..740059ee60 100644 --- a/module/kanban/ui/create.html.php +++ b/module/kanban/ui/create.html.php @@ -366,12 +366,19 @@ modalTrigger setClass('ml-2.5 mr-2'), $lang->kanban->copyContent, ), - checkList + checkBox ( - set::name('copyContent[]'), - set::items($lang->kanban->copyContentList), + set::text($lang->kanban->copyContentList['basicInfo']), set::inline(true), - set::value('basicInfo') + set::checked(true), + set::disabled(true) + ), + checkBox + ( + set::name('copyRegionInfo'), + set::rootClass('ml-2'), + set::text($lang->kanban->copyContentList['region']), + set::inline(true) ) ), div diff --git a/module/kanban/ui/setting.html.php b/module/kanban/ui/setting.html.php index 51ae174100..dc3bb32a56 100644 --- a/module/kanban/ui/setting.html.php +++ b/module/kanban/ui/setting.html.php @@ -91,7 +91,7 @@ formPanel ), formRow ( - set::style(array('margin-top' => '0px')), + set::style(array('margin-top' => '0px', 'display' => $enableImport == 'off' ? 'none' : 'block')), setID('objectBox'), formGroup ( diff --git a/module/message/test/model/getobjectactions.php b/module/message/test/model/getobjectactions.php index 4fa957f5b5..49e783ec4d 100755 --- a/module/message/test/model/getobjectactions.php +++ b/module/message/test/model/getobjectactions.php @@ -4,6 +4,7 @@ declare(strict_types=1); include dirname(__FILE__, 5) . '/test/lib/init.php'; include dirname(__FILE__, 2) . '/message.class.php'; +zdTable('lang')->gen(0); zdTable('user')->gen(1); su('admin'); @@ -16,8 +17,12 @@ pid=1 */ -$message = new messageTest(); +global $lang, $app, $conifg; +$lang->SRCommon = '研发需求'; +$app::$loadedLangs = array(); +$app->loadLang('message'); +$message = new messageTest(); $objectActions = $message->getObjectActionsTest(); r($objectActions) && p('product:opened') && e('创建'); //查询objectType为product的action是opened的lab标签 diff --git a/module/message/test/model/getobjecttypes.php b/module/message/test/model/getobjecttypes.php index cd2496d1fd..2f4426555a 100755 --- a/module/message/test/model/getobjecttypes.php +++ b/module/message/test/model/getobjecttypes.php @@ -4,6 +4,7 @@ declare(strict_types=1); include dirname(__FILE__, 5) . '/test/lib/init.php'; include dirname(__FILE__, 2) . '/message.class.php'; +zdTable('lang')->gen(0); zdTable('user')->gen(1); su('admin'); @@ -16,8 +17,12 @@ pid=1 */ -$message = new messageTest(); +global $lang, $app, $conifg; +$lang->SRCommon = '研发需求'; +$app::$loadedLangs = array(); +$app->loadLang('message'); +$message = new messageTest(); $objectTypes = $message->getObjectTypesTest(); r($objectTypes) && p('product') && e('产品'); //查询objectType为product的objectTypes diff --git a/module/metric/model.php b/module/metric/model.php index 48d4b5b57a..5a209336c9 100755 --- a/module/metric/model.php +++ b/module/metric/model.php @@ -161,12 +161,13 @@ class metricModel extends model $groupHeader = array(); - $groupHeader[] = array('name' => 'date', 'title' => $this->lang->metric->$dateType, 'align' => 'center', 'width' => 96); + $groupHeader[] = array('name' => 'date', 'title' => zget($this->lang->metric, $dateType, ''), 'align' => 'center', 'width' => 96); $groupHeader[] = array('name' => 'value', 'title' => $this->lang->metric->value, 'align' => 'center', 'width' => 68); $groupData = array(); foreach($data as $dataInfo) { + $date = ''; if($dateType == 'year') { $date = substr($dataInfo->$dateField, 0, 4); diff --git a/module/metric/test/calc/project/hour/ac_of_all_in_waterfall.php b/module/metric/test/calc/project/hour/ac_of_all_in_waterfall.php index df8c85ce03..e693bff5c5 100755 --- a/module/metric/test/calc/project/hour/ac_of_all_in_waterfall.php +++ b/module/metric/test/calc/project/hour/ac_of_all_in_waterfall.php @@ -8,7 +8,7 @@ timeout=0 cid=1 - 测试分组数。 @5 -- 测试项目7。第0条的value属性 @174.5 +- 测试项目7。第0条的value属性 @88 */ include dirname(__FILE__, 7) . '/test/lib/init.php'; @@ -23,4 +23,4 @@ $calc = $metric->calcMetric(__FILE__); r(count($calc->getResult())) && p('') && e('5'); // 测试分组数。 -r($calc->getResult(array('project' => '7'))) && p('0:value') && e('174.5'); // 测试项目7。 \ No newline at end of file +r($calc->getResult(array('project' => '7'))) && p('0:value') && e('88'); // 测试项目7。 diff --git a/module/metric/test/calc/project/hour/consume_of_all_in_project.php b/module/metric/test/calc/project/hour/consume_of_all_in_project.php index 7f35c5d92c..56b24c9e92 100755 --- a/module/metric/test/calc/project/hour/consume_of_all_in_project.php +++ b/module/metric/test/calc/project/hour/consume_of_all_in_project.php @@ -8,7 +8,7 @@ timeout=0 cid=1 - 测试分组数。 @5 -- 测试项目7。第0条的value属性 @174.5 +- 测试项目7。第0条的value属性 @88 */ include dirname(__FILE__, 7) . '/test/lib/init.php'; @@ -23,4 +23,4 @@ $calc = $metric->calcMetric(__FILE__); r(count($calc->getResult())) && p('') && e('5'); // 测试分组数。 -r($calc->getResult(array('project' => '7'))) && p('0:value') && e('174.5'); // 测试项目7。 \ No newline at end of file +r($calc->getResult(array('project' => '7'))) && p('0:value') && e('88'); // 测试项目7。 diff --git a/module/metric/test/calc/project/hour/day_of_invested_in_project.php b/module/metric/test/calc/project/hour/day_of_invested_in_project.php index 4b85613ad2..88f9ab72c5 100755 --- a/module/metric/test/calc/project/hour/day_of_invested_in_project.php +++ b/module/metric/test/calc/project/hour/day_of_invested_in_project.php @@ -8,7 +8,7 @@ timeout=0 cid=1 - 测试分组数。 @5 -- 测试项目7。第0条的value属性 @24.93 +- 测试项目7。第0条的value属性 @12.57 */ include dirname(__FILE__, 7) . '/test/lib/init.php'; @@ -23,4 +23,4 @@ $calc = $metric->calcMetric(__FILE__); r(count($calc->getResult())) && p('') && e('5'); // 测试分组数。 -r($calc->getResult(array('project' => '7'))) && p('0:value') && e('24.93'); // 测试项目7。 \ No newline at end of file +r($calc->getResult(array('project' => '7'))) && p('0:value') && e('12.57'); // 测试项目7。 diff --git a/module/metric/test/calc/user/scale/count_of_daily_review_feedback_in_user.php b/module/metric/test/calc/user/scale/count_of_daily_review_feedback_in_user.php index 33b85784a2..74933876d4 100755 --- a/module/metric/test/calc/user/scale/count_of_daily_review_feedback_in_user.php +++ b/module/metric/test/calc/user/scale/count_of_daily_review_feedback_in_user.php @@ -7,7 +7,7 @@ title=count_of_daily_review_feedback_in_user timeout=0 cid=1 -- 测试分组数。 @224 +- 测试分组数。 @244 - 测试用户admin @10 - 测试用户user @42 - 测试用户dev @64 @@ -24,10 +24,10 @@ zdTable('feedback')->config('feedback', true, 4)->gen(1000); $metric = new metricTest(); $calc = $metric->calcMetric(__FILE__); -r(count($calc->getResult())) && p('') && e('224'); // 测试分组数。 +r(count($calc->getResult())) && p('') && e('244'); // 测试分组数。 r(count($calc->getResult(array('user' => 'admin')))) && p('') && e('10'); // 测试用户admin r(count($calc->getResult(array('user' => 'user')))) && p('') && e('42'); // 测试用户user r(count($calc->getResult(array('user' => 'dev')))) && p('') && e('64'); // 测试用户dev r(count($calc->getResult(array('user' => 'pm')))) && p('') && e('48'); // 测试用户pm -r($calc->getResult(array('user' => 'notexist'))) && p('') && e('0'); // 测试不存在的用户 \ No newline at end of file +r($calc->getResult(array('user' => 'notexist'))) && p('') && e('0'); // 测试不存在的用户 diff --git a/module/metric/test/calc/user/scale/count_of_dealyed_task_in_user.php b/module/metric/test/calc/user/scale/count_of_dealyed_task_in_user.php index fc199b5cbb..f383b5e2eb 100755 --- a/module/metric/test/calc/user/scale/count_of_dealyed_task_in_user.php +++ b/module/metric/test/calc/user/scale/count_of_dealyed_task_in_user.php @@ -17,10 +17,10 @@ timeout=0 cid=1 - 测试分组数。 @8 -- 测试用户dev。第0条的value属性 @27 +- 测试用户dev。第0条的value属性 @25 */ r(count($calc->getResult())) && p('') && e('8'); // 测试分组数。 -r($calc->getResult(array('user' => 'dev'))) && p('0:value') && e('27'); // 测试用户dev。 \ No newline at end of file +r($calc->getResult(array('user' => 'dev'))) && p('0:value') && e('25'); // 测试用户dev。 diff --git a/module/metric/test/model/getmetricbycode.php b/module/metric/test/model/getmetricbycode.php old mode 100755 new mode 100644 index 80992d4828..54ff5f479f --- a/module/metric/test/model/getmetricbycode.php +++ b/module/metric/test/model/getmetricbycode.php @@ -3,7 +3,10 @@ include dirname(__FILE__, 5) . '/test/lib/init.php'; include dirname(__FILE__, 2) . '/calc.class.php'; -// zdTable('feedback')->config('feedback_create')->gen(5000); +zdTable('user')->config('user', true)->gen(30); +zdTable('product')->config('product', true)->gen(10); +zdTable('productplan')->config('productplan', true)->gen(50); +zdTable('feedback')->config('feedback_create', true)->gen(50); $metric = new metricTest(); @@ -13,13 +16,14 @@ title=getMetricByCode timeout=0 cid=1 -- 测试按产品统计的计划数第0条的value属性 @50 -- 测试按产品统计年度关闭反馈数第0条的value属性 @0 +- 测试按产品统计的计划数第0条的value属性 @2 +- 测试按产品统计年度关闭反馈数第0条的value属性 @4 */ -$options = array('product' => '7'); -r($metric->getMetricByCode('count_of_plan_in_product', $options)) && p('0:value') && e('50'); // 测试按产品统计的计划数 -$options = array('product' => '9', 'year' => '2014'); -r($metric->getMetricByCode('count_of_annual_closed_feedback_in_product', $options)) && p('0:value') && e('0'); // 测试按产品统计年度关闭反馈数 \ No newline at end of file +global $config; +$config->vision = 'rnd'; + +$options = array('product' => '9', 'year' => date('Y')); +r($metric->getMetricByCode('count_of_annual_closed_feedback_in_product', $options)) && p('0:value') && e('4'); // 测试按产品统计年度关闭反馈数 diff --git a/module/metric/test/model/getresultbycode.php b/module/metric/test/model/getresultbycode.php new file mode 100755 index 0000000000..b88110f9d9 --- /dev/null +++ b/module/metric/test/model/getresultbycode.php @@ -0,0 +1,28 @@ +#!/usr/bin/env php +config('user', true)->gen(30); +zdTable('product')->config('product', true)->gen(10); +zdTable('productplan')->config('productplan', true)->gen(50); +zdTable('feedback')->config('feedback_create', true)->gen(50); + +$metric = new metricTest(); + +/** + +title=getMetricByCode +timeout=0 +cid=1 + +- 测试按产品统计的计划数第0条的value属性 @2 +- 测试按产品统计年度关闭反馈数第0条的value属性 @4 + +*/ + +$options = array('product' => '7'); +r($metric->getMetricByCode('count_of_productplan_in_product', $options)) && p('0:value') && e('2'); // 测试按产品统计的计划数 + +$options = array('product' => '9', 'year' => date('Y')); +r($metric->getMetricByCode('count_of_annual_closed_feedback_in_product', $options)) && p('0:value') && e('4'); // 测试按产品统计年度关闭反馈数 diff --git a/module/misc/lang/de.php b/module/misc/lang/de.php index 571b87c698..41a971daff 100644 --- a/module/misc/lang/de.php +++ b/module/misc/lang/de.php @@ -113,7 +113,7 @@ $lang->misc->feature->promptExecImage = 'theme/default/images/main/prompt_exec $lang->misc->feature->promptLearnMore = 'https://www.zentao.net/book/zentaopms/1097.html'; /* Release Date. */ -$lang->misc->releaseDate['20.0.beta2'] = '2024-03-07'; +$lang->misc->releaseDate['20.0.beta2'] = '2024-03-15'; $lang->misc->releaseDate['18.11'] = '2024-02-28'; $lang->misc->releaseDate['18.10.1'] = '2024-01-17'; $lang->misc->releaseDate['20.0.beta1'] = '2024-01-26'; @@ -224,7 +224,7 @@ $lang->misc->releaseDate['7.1.stable'] = '2015-03-07'; $lang->misc->releaseDate['6.3.stable'] = '2014-11-07'; /* Release Detail. */ -$lang->misc->feature->all['20.0.beta2'][] = array('title' => '', 'desc' => ''); +$lang->misc->feature->all['20.0.beta2'][] = array('title' => 'Enhance the functionality and fine-tune the details, while addressing any known bugs.', 'desc' => ''); $lang->misc->feature->all['18.11'][] = array('title' => "We have successfully implemented AI mini-program functionality, integrating measurement metrics and global filtering capabilities into the large-screen display. Additionally, we have added feedback functionality to the OR interface. Furthermore, we have enabled the inclusion of keywords in the Requirement Management Hub, allowing for the redistribution of requirements in the Requirement Management Hub once a user's requirement has been withdrawn.", 'desc' => ''); $lang->misc->feature->all['18.10.1'][] = array('title' => 'The Requirement Management Hub (RM Hub) now includes notification functionality, supports product line features, and allows for distributing a single requirement to multiple products.', 'desc' => ''); $lang->misc->feature->all['20.0.beta1'][] = array('title' => 'This release mainly involves a large-scale refactoring of the code and a brand new upgrade to the user interface (UI), bringing better product performance, stronger security, and a more user-friendly experience.', 'desc' => ''); diff --git a/module/misc/lang/en.php b/module/misc/lang/en.php index ce5e11e1fc..912ea4b067 100644 --- a/module/misc/lang/en.php +++ b/module/misc/lang/en.php @@ -113,7 +113,7 @@ $lang->misc->feature->promptExecImage = 'theme/default/images/main/prompt_exec $lang->misc->feature->promptLearnMore = 'https://www.zentao.net/book/zentaopms/1097.html'; /* Release Date. */ -$lang->misc->releaseDate['20.0.beta2'] = '2024-03-07'; +$lang->misc->releaseDate['20.0.beta2'] = '2024-03-15'; $lang->misc->releaseDate['18.11'] = '2024-02-28'; $lang->misc->releaseDate['18.10.1'] = '2024-01-17'; $lang->misc->releaseDate['20.0.beta1'] = '2024-01-26'; @@ -224,7 +224,7 @@ $lang->misc->releaseDate['7.1.stable'] = '2015-03-07'; $lang->misc->releaseDate['6.3.stable'] = '2014-11-07'; /* Release Detail. */ -$lang->misc->feature->all['20.0.beta2'][] = array('title' => '', 'desc' => ''); +$lang->misc->feature->all['20.0.beta2'][] = array('title' => 'Enhance the functionality and fine-tune the details, while addressing any known bugs.', 'desc' => ''); $lang->misc->feature->all['18.11'][] = array('title' => "We have successfully implemented AI mini-program functionality, integrating measurement metrics and global filtering capabilities into the large-screen display. Additionally, we have added feedback functionality to the OR interface. Furthermore, we have enabled the inclusion of keywords in the Requirement Management Hub, allowing for the redistribution of requirements in the Requirement Management Hub once a user's requirement has been withdrawn.", 'desc' => ''); $lang->misc->feature->all['18.10.1'][] = array('title' => 'The Requirement Management Hub (RM Hub) now includes notification functionality, supports product line features, and allows for distributing a single requirement to multiple products.', 'desc' => ''); $lang->misc->feature->all['20.0.beta1'][] = array('title' => 'This release mainly involves a large-scale refactoring of the code and a brand new upgrade to the user interface (UI), bringing better product performance, stronger security, and a more user-friendly experience.', 'desc' => ''); diff --git a/module/misc/lang/fr.php b/module/misc/lang/fr.php index 5539170651..4c12f2bbef 100644 --- a/module/misc/lang/fr.php +++ b/module/misc/lang/fr.php @@ -113,7 +113,7 @@ $lang->misc->feature->promptExecImage = 'theme/default/images/main/prompt_exec $lang->misc->feature->promptLearnMore = 'https://www.zentao.net/book/zentaopms/1097.html'; /* Release Date. */ -$lang->misc->releaseDate['20.0.beta2'] = '2024-03-07'; +$lang->misc->releaseDate['20.0.beta2'] = '2024-03-15'; $lang->misc->releaseDate['18.11'] = '2024-02-28'; $lang->misc->releaseDate['18.10.1'] = '2024-01-17'; $lang->misc->releaseDate['20.0.beta1'] = '2024-01-26'; @@ -224,7 +224,7 @@ $lang->misc->releaseDate['7.1.stable'] = '2015-03-07'; $lang->misc->releaseDate['6.3.stable'] = '2014-11-07'; /* Release Detail. */ -$lang->misc->feature->all['20.0.beta2'][] = array('title' => '', 'desc' => ''); +$lang->misc->feature->all['20.0.beta2'][] = array('title' => 'Enhance the functionality and fine-tune the details, while addressing any known bugs.', 'desc' => ''); $lang->misc->feature->all['18.11'][] = array('title' => "We have successfully implemented AI mini-program functionality, integrating measurement metrics and global filtering capabilities into the large-screen display. Additionally, we have added feedback functionality to the OR interface. Furthermore, we have enabled the inclusion of keywords in the Requirement Management Hub, allowing for the redistribution of requirements in the Requirement Management Hub once a user's requirement has been withdrawn.", 'desc' => ''); $lang->misc->feature->all['18.10.1'][] = array('title' => 'The Requirement Management Hub (RM Hub) now includes notification functionality, supports product line features, and allows for distributing a single requirement to multiple products.', 'desc' => ''); $lang->misc->feature->all['20.0.beta1'][] = array('title' => 'This release mainly involves a large-scale refactoring of the code and a brand new upgrade to the user interface (UI), bringing better product performance, stronger security, and a more user-friendly experience.', 'desc' => ''); diff --git a/module/misc/lang/zh-cn.php b/module/misc/lang/zh-cn.php index b1a34ea060..025cbf0d57 100644 --- a/module/misc/lang/zh-cn.php +++ b/module/misc/lang/zh-cn.php @@ -113,7 +113,7 @@ $lang->misc->feature->promptExecImage = 'theme/default/images/main/prompt_exec $lang->misc->feature->promptLearnMore = 'https://www.zentao.net/book/zentaopms/1097.html'; /* Release Date. */ -$lang->misc->releaseDate['20.0.beta2'] = '2024-03-07'; +$lang->misc->releaseDate['20.0.beta2'] = '2024-03-15'; $lang->misc->releaseDate['18.11'] = '2024-02-28'; $lang->misc->releaseDate['18.10.1'] = '2024-01-17'; $lang->misc->releaseDate['20.0.beta1'] = '2024-01-26'; @@ -224,7 +224,7 @@ $lang->misc->releaseDate['7.1.stable'] = '2015-03-07'; $lang->misc->releaseDate['6.3.stable'] = '2014-11-07'; /* Release Detail. */ -$lang->misc->feature->all['20.0.beta2'][] = array('title' => '', 'desc' => ''); +$lang->misc->feature->all['20.0.beta2'][] = array('title' => '完善细节功能,解已知Bug。', 'desc' => ''); $lang->misc->feature->all['18.11'][] = array('title' => '实现AI小程序功能,实现大屏引用度量项、全局筛选器功能,OR界面实现反馈功能,需求池需求增加关键词,撤回用户需求后需求池需求可以再次分发。', 'desc' => ''); $lang->misc->feature->all['18.10.1'][] = array('title' => '需求池增加通知功能,支持产品线功能,支持一个需求分发到多个产品。', 'desc' => ''); $lang->misc->feature->all['20.0.beta1'][] = array('title' => '本次发布主要对代码进行了大规模重构,同时对用户界面(UI)进行了全新升级,带来了更好的产品性能、更强的安全性和更友好的用户体验。', 'desc' => ''); diff --git a/module/my/config/table.php b/module/my/config/table.php index 8d699e7f02..a457c374e2 100644 --- a/module/my/config/table.php +++ b/module/my/config/table.php @@ -339,10 +339,11 @@ $config->my->requirement->actionList['review']['hint'] = $lang->story->re $config->my->requirement->actionList['review']['url'] = array('module' => 'story', 'method' => 'review', 'params' => 'storyID={id}&from=product&storyType=requirement'); $config->my->requirement->actionList['review']['data-toggle'] = 'modal'; -$config->my->requirement->actionList['recall']['icon'] = 'undo'; -$config->my->requirement->actionList['recall']['text'] = $lang->story->recall; -$config->my->requirement->actionList['recall']['hint'] = $lang->story->recall; -$config->my->requirement->actionList['recall']['url'] = array('module' => 'story', 'method' => 'recall', 'params' => 'storyID={id}&from=list&confirm=no&storyType=requirement'); +$config->my->requirement->actionList['recall']['icon'] = 'undo'; +$config->my->requirement->actionList['recall']['text'] = $lang->story->recall; +$config->my->requirement->actionList['recall']['hint'] = $lang->story->recall; +$config->my->requirement->actionList['recall']['url'] = array('module' => 'story', 'method' => 'recall', 'params' => 'storyID={id}&from=list&confirm=no&storyType=requirement'); +$config->my->requirement->actionList['recall']['className'] = 'ajax-submit'; $config->my->requirement->actionList['edit']['icon'] = 'edit'; $config->my->requirement->actionList['edit']['text'] = $lang->story->edit; @@ -427,10 +428,11 @@ $config->my->story->actionList['review']['hint'] = $lang->story->review; $config->my->story->actionList['review']['url'] = array('module' => 'story', 'method' => 'review', 'params' => 'storyID={id}'); $config->my->story->actionList['review']['data-toggle'] = 'modal'; -$config->my->story->actionList['recall']['icon'] = 'undo'; -$config->my->story->actionList['recall']['text'] = $lang->story->recall; -$config->my->story->actionList['recall']['hint'] = $lang->story->recall; -$config->my->story->actionList['recall']['url'] = array('module' => 'story', 'method' => 'recall', 'params' => 'storyID={id}'); +$config->my->story->actionList['recall']['icon'] = 'undo'; +$config->my->story->actionList['recall']['text'] = $lang->story->recall; +$config->my->story->actionList['recall']['hint'] = $lang->story->recall; +$config->my->story->actionList['recall']['url'] = array('module' => 'story', 'method' => 'recall', 'params' => 'storyID={id}'); +$config->my->story->actionList['recall']['className'] = 'ajax-submit'; $config->my->story->actionList['edit']['icon'] = 'edit'; $config->my->story->actionList['edit']['text'] = $lang->story->edit; diff --git a/module/my/control.php b/module/my/control.php index bf39cdc048..ebe0d225e0 100755 --- a/module/my/control.php +++ b/module/my/control.php @@ -1453,6 +1453,7 @@ class my extends control { $_SESSION['vision'] = $vision; $this->loadModel('setting')->setItem("{$this->app->user->account}.common.global.vision", $vision); + if(empty($this->config->hideVisionTips)) $this->setting->setItem("{$this->app->user->account}.common.global.hideVisionTips", 1); $this->config->vision = $vision; $_SESSION['user']->rights = $this->user->authorize($this->app->user->account); @@ -1461,4 +1462,18 @@ class my extends control return $this->send(array('result' => 'success', 'load' => helper::createLink('index', 'index'))); } + + /** + * Ajax: 保存界面切换引导. + * Save hideVisionTips by ajax. + * + * @param string $vision + * @access public + * @return void + */ + public function ajaxSaveVisionTips() + { + $this->loadModel('setting')->setItem("{$this->app->user->account}.common.global.hideVisionTips", 1); + return $this->send(array('result' => 'success', 'load' => helper::createLink('index', 'index'))); + } } diff --git a/module/my/js/bug.ui.js b/module/my/js/bug.ui.js index 1a0bb20781..c7a6405b6b 100644 --- a/module/my/js/bug.ui.js +++ b/module/my/js/bug.ui.js @@ -41,5 +41,5 @@ window.setStatistics = function(element, checks) { if(checks.length) return checkedSummary.replace('{checked}', checks.length); - return element.options.customData.pageSummary; + return element.options.customData ? element.options.customData.pageSummary : null; } diff --git a/module/my/js/story.ui.js b/module/my/js/story.ui.js index 9a74e9efc2..346eeec49b 100644 --- a/module/my/js/story.ui.js +++ b/module/my/js/story.ui.js @@ -32,6 +32,7 @@ window.renderCell = function(result, info) if(info.col.name == 'title' && result[0]) { const story = info.row.data; + if(story.shadow) result[0].props.href += '#app=project'; if(story.parent > 0) { let html = "" + childrenAB + ""; diff --git a/module/my/test/model/getactions.php b/module/my/test/model/getactions.php index 00a4ef8009..2291809653 100755 --- a/module/my/test/model/getactions.php +++ b/module/my/test/model/getactions.php @@ -21,6 +21,11 @@ zdTable('user')->gen('1'); su('admin'); +global $lang, $app; +$lang->SRCommon = '研发需求'; +$lang->URCommon = '用户需求'; +$app->loadLang('action'); + $my = new myTest(); $actions = $my->getActionsTest(); diff --git a/module/my/ui/bug.html.php b/module/my/ui/bug.html.php index de97bdde5e..8838009cb9 100644 --- a/module/my/ui/bug.html.php +++ b/module/my/ui/bug.html.php @@ -93,6 +93,8 @@ dtable ( set::cols($cols), set::data(array_values($bugs)), + set::priList($lang->bug->priList), + set::severityList($lang->bug->severityList), set::userMap($users), set::fixedLeftWidth('44%'), set::onRenderCell(jsRaw('window.onRenderBugNameCell')), @@ -104,7 +106,7 @@ dtable set::footToolbar($footToolbar), set::footPager(usePager()), set::emptyTip($lang->bug->notice->noBug), - set::customData($type == 'resolvedBy' ? array('pageSummary' => $summary) : null) + set::customData($type == 'resolvedBy' ? array('pageSummary' => $summary) : array()) ); render(); diff --git a/module/my/ui/todo.html.php b/module/my/ui/todo.html.php index 222cae6365..0f6306a26b 100644 --- a/module/my/ui/todo.html.php +++ b/module/my/ui/todo.html.php @@ -107,6 +107,7 @@ dtable set::cols($cols), set::data($data), set::userMap($users), + set::priList($lang->todo->priList), set::fixedLeftWidth('44%'), set::checkable(true), set::defaultSummary(array('html' => $defaultSummary)), diff --git a/module/my/zen.php b/module/my/zen.php index 4f8de04d7f..24cf6bd8a8 100644 --- a/module/my/zen.php +++ b/module/my/zen.php @@ -28,7 +28,7 @@ class myZen extends my $task->consumedLabel = $task->consumed . $this->lang->execution->workHourUnit; $task->leftLabel = $task->left . $this->lang->execution->workHourUnit; $task->status = !empty($task->storyStatus) && $task->storyStatus == 'active' && $task->latestStoryVersion > $task->storyVersion && !in_array($task->status, array('cancel', 'closed')) ? $this->lang->my->storyChanged : $task->status; - if($task->parent) + if($task->parent > 0) { if(isset($tasks[$task->parent])) { diff --git a/module/pipeline/model.php b/module/pipeline/model.php index abd4f3f12a..ac91898107 100755 --- a/module/pipeline/model.php +++ b/module/pipeline/model.php @@ -184,7 +184,7 @@ class pipelineModel extends model $this->dao->update(TABLE_PIPELINE)->set('deleted')->eq(1)->where('id')->eq($id)->exec(); - $this->loadModel('action')->create($type, $id, 'deleted', ''); + $this->loadModel('action')->create($type, $id, 'deleted', '', 1); return $this->dao->lastInsertID(); } diff --git a/module/pivot/test/model/gensheet.php b/module/pivot/test/model/gensheet.php index 8c2a156d29..de6b7c25e3 100644 --- a/module/pivot/test/model/gensheet.php +++ b/module/pivot/test/model/gensheet.php @@ -97,4 +97,4 @@ $array = array_map(function($item){ return str_replace('%', '', $item); }, $item); }, $data->array); -r($array) && p('0:self_bydesign_slice_resolution0,bydesign_slice_resolution0,self_duplicate_slice_resolution0,duplicate_slice_resolution0,willnotfix_slice_resolution0,self_willnotfix_slice_resolution0') && e('2,22.22,1,11.11,11.11,1'); //查看id为1025的透视表生成的切片数据是否正确。 +r($array) && p('0:self_bydesign_slice_resolution0,bydesign_slice_resolution0,self_duplicate_slice_resolution0,duplicate_slice_resolution0,willnotfix_slice_resolution0,self_willnotfix_slice_resolution0') && e('2,13.33,2,13.33,13.33,2'); //查看id为1025的透视表生成的切片数据是否正确。 diff --git a/module/pivot/test/model/getbugassign.php b/module/pivot/test/model/getbugassign.php index e0d11c7c72..bd7e7bd178 100644 --- a/module/pivot/test/model/getbugassign.php +++ b/module/pivot/test/model/getbugassign.php @@ -28,7 +28,7 @@ $pivot = new pivotTest(); $result = $pivot->getBugAssign(); r($result) && p('0:product,assignedTo,total;9:product,assignedTo,total') && e('1,admin,10;10,admin,10'); //判断已分配的bug数据获取是否正确。 -foreach($result as $row) +foreach($result as $row) { $productName = $row->productName; $row->isProductNameHtml = false; @@ -45,7 +45,7 @@ r($condition1 && $condition2) && p('') && e('1'); //1.用户有此产品的权 su('user1'); $result = $pivot->getBugAssign(); -foreach($result as $row) +foreach($result as $row) { $productName = $row->productName; $row->isProductNameHtml = false; diff --git a/module/pivot/test/yaml/project.yaml b/module/pivot/test/yaml/project.yaml index 1da1ce046c..149a11b62d 100644 --- a/module/pivot/test/yaml/project.yaml +++ b/module/pivot/test/yaml/project.yaml @@ -9,4 +9,4 @@ fields: - field: type range: project{100} - field: hasProduct - range: 0{5},{1} + range: 0{5},1{1} diff --git a/module/product/js/browse.ui.js b/module/product/js/browse.ui.js index c54c9ab14c..88f573014f 100644 --- a/module/product/js/browse.ui.js +++ b/module/product/js/browse.ui.js @@ -62,7 +62,7 @@ window.renderCell = function(result, info) { const story = info.row.data; let html = ''; - if(story.parent > 0 && $.cookie.get('tab') == 'project') html += story.parentName + ' / '; + if(story.parentName != undefined && story.parent > 0 && $.cookie.get('tab') == 'project' && vision != 'lite') html += story.parentName + ' / '; if(typeof modulePairs[story.rawModule] != 'undefined') html += "" + modulePairs[story.rawModule] + " "; if(story.parent > 0) html += "" + (storyType == 'requirement' ? 'SR' : childrenAB) + " "; if(story.color) result[0].props.style = 'color: ' + story.color; diff --git a/module/product/js/project.ui.js b/module/product/js/project.ui.js index 28a8a7fc49..85131757e0 100644 --- a/module/product/js/project.ui.js +++ b/module/product/js/project.ui.js @@ -23,8 +23,6 @@ window.link2Project = function(e) $.get($.createLink('project', 'ajaxGetLinkedProducts', 'projectID=' + selectProjectID), function(product) { - let products = []; - let branches = []; const formData = new FormData(); var linkedProducts = JSON.parse(product); @@ -33,12 +31,12 @@ window.link2Project = function(e) for(var branchID in linkedProducts[productID]) { formData.append('products[]', productID); - formData.append('branch[' + productID + '][]', branchID); + formData.append('branch[]', branchID); } } formData.append('products[]', currentProductID); - formData.append('branch[' + currentProductID + '][]', currentBranchID); + formData.append('branch[]', currentBranchID); $.ajaxSubmit({ url: $.createLink('project', 'manageProducts', 'projectID=' + selectProjectID), diff --git a/module/product/ui/browse.html.php b/module/product/ui/browse.html.php index 6a1aa51a6d..549465c23f 100644 --- a/module/product/ui/browse.html.php +++ b/module/product/ui/browse.html.php @@ -15,6 +15,7 @@ namespace zin; data('storyType', $storyType); data('activeMenuID', $storyType); jsVar('URChanged', $this->lang->story->URChanged); +jsVar('vision', $config->vision); $storyCommon = $storyType == 'requirement' ? $lang->URCommon : $lang->SRCommon; $isProjectStory = $this->app->rawModule == 'projectstory'; @@ -126,7 +127,7 @@ $fnBuildCreateStoryButton = function() use ($lang, $product, $isProjectStory, $s }; /* Build link story button. */ -$fnBuildLinkStoryButton = function() use($lang, $app, $product, $projectHasProduct, $project) +$fnBuildLinkStoryButton = function() use($lang, $app, $product, $projectHasProduct, $project, $storyType) { if(!common::canModify('product', $product)) return null; @@ -144,9 +145,11 @@ $fnBuildLinkStoryButton = function() use($lang, $app, $product, $projectHasProdu ))); } + if($storyType == 'requirement') $lang->execution->linkStory = str_replace($lang->SRCommon, $lang->URCommon, $lang->execution->linkStory); + $canLinkStory = common::hasPriv('projectstory', 'linkStory'); - $canlinkPlanStory = !empty($product) && common::hasPriv('projectstory', 'importPlanStories'); - $linkStoryUrl = $this->createLink('projectstory', 'linkStory', "project=$project->id"); + $canlinkPlanStory = !empty($product) && common::hasPriv('projectstory', 'importPlanStories') && $storyType == 'story'; + $linkStoryUrl = $this->createLink('projectstory', 'linkStory', "project=$project->id&browseType=¶m=0&recTotal=0&recPerPage=50&pageID=1&storyType=$storyType"); $linkItem = array('text' => $lang->execution->linkStory, 'url' => $linkStoryUrl); $linkPlanItem = array('text' => $lang->execution->linkStoryByPlan, 'url' => '#linkStoryByPlan', 'data-toggle' => 'modal', 'data-size' => 'sm'); if($canLinkStory && $canlinkPlanStory) @@ -190,6 +193,7 @@ $options = array('storyTasks' => $storyTasks, 'storyBugs' => $storyBugs, 'storyC foreach($stories as $story) { $story->rawModule = $story->module; + $story->from = $app->tab; $options['branches'] = zget($branchOptions, $story->product, array()); $data[] = $this->story->formatStoryForList($story, $options, $storyType); if(!isset($story->children)) continue; @@ -197,6 +201,7 @@ foreach($stories as $story) /* Children. */ foreach($story->children as $key => $child) { + if($app->rawModule == 'projectstory' && $child->project != $story->project) continue; $child->rawModule = $child->module; $data[] = $this->story->formatStoryForList($child, $options, $storyType); } @@ -322,7 +327,7 @@ featureBar toolbar ( - (!hasPriv('story', 'report') || !$productID) ? null : item(set(array('id' => 'reportBtn', 'text' => $lang->story->report->common, 'icon' => 'bar-chart', 'class' => 'ghost', 'url' => helper::createLink('story', 'report', "productID=$productID&branchID=$branch&storyType=$storyType&browseType=$browseType&moduleID=$moduleID&chartType=pie&projectID=$projectID" . ($app->tab == 'project' ? '#app=project' : ''))))), + (!hasPriv('story', 'report') || !$productID) ? null : item(set(array('id' => 'reportBtn', 'text' => $lang->story->report->common, 'icon' => 'bar-chart', 'class' => 'ghost', 'url' => helper::createLink('story', 'report', "productID=$productID&branchID=$branch&storyType=$storyType&browseType=$browseType&moduleID=$moduleID&chartType=pie&projectID=$projectID") . ($app->tab == 'project' ? '#app=project' : '')))), !hasPriv('story', 'export') ? null : item(set(array('id' => 'exportBtn', 'text' => $lang->export, 'icon' => 'export', 'class' => 'ghost', 'url' => helper::createLink('story', 'export', "productID=$productID&orderBy=$orderBy&executionID=$projectID&browseType=$browseType&storyType=$storyType"), 'data-toggle' => 'modal'))), $fnBuildCreateStoryButton(), $fnBuildLinkStoryButton() @@ -359,25 +364,6 @@ $linkStoryByPlanTips = $lang->execution->linkNormalStoryByPlanTips; if($product && $product->type != 'normal') $linkStoryByPlanTips = sprintf($lang->execution->linkBranchStoryByPlanTips, $lang->product->branchName[$product->type]); if($isProjectStory) $linkStoryByPlanTips = str_replace($lang->execution->common, $lang->projectCommon, $linkStoryByPlanTips); -if($app->tab == 'project') -{ - $projectPlans = array(); - foreach($products as $product) - { - if(empty($product->plans)) continue; - - foreach($product->plans as $planIDList) - { - $planIDList = explode(',', $planIDList); - foreach($planIDList as $plan) - { - if(in_array($plan, array_keys($plans))) $projectPlans[$plan] = $plans[$plan]; - } - } - } - $plans = $projectPlans; -} - modal ( setID('linkStoryByPlan'), diff --git a/module/product/zen.php b/module/product/zen.php index f629948898..aa08dc946a 100644 --- a/module/product/zen.php +++ b/module/product/zen.php @@ -1419,8 +1419,6 @@ class productZen extends product $showModule = empty($this->config->product->browse->showModule) ? 0 : $this->config->product->browse->showModule; if($isProjectStory) $showModule = empty($this->config->projectstory->story->showModule) ? 0 : $this->config->projectstory->story->showModule; - if($projectID) $this->view->products = $this->loadModel('product')->getProducts($projectID); - $this->view->title = $productName . $this->lang->colon . ($storyType === 'story' ? $this->lang->product->browse : $this->lang->product->requirement); $this->view->productID = $productID; $this->view->product = $product; @@ -1438,7 +1436,7 @@ class productZen extends product $this->view->projectProducts = $projectProducts; $this->view->summary = $this->product->summary($stories, $storyType); - $this->view->plans = $this->loadModel('productplan')->getPairs($productID, ($branch === 'all' || empty($branch)) ? '' : $branch, 'unexpired,noclosed', true); + $this->view->plans = $this->loadModel('productplan')->getPairs($productID, isset($projectProducts[$productID]) ? array(BRANCH_MAIN) + $projectProducts[$productID]->branches : (($branch === 'all' || empty($branch)) ? '' : $branch), 'unexpired,noclosed', true); $this->view->users = $this->loadModel('user')->getPairs('noletter|pofirst|nodeleted'); $this->view->modules = $this->tree->getOptionMenu($productID, 'story', 0, $branchID); $this->view->storyTasks = $this->loadModel('task')->getStoryTaskCounts($storyIdList); diff --git a/module/productplan/ui/browsebylist.html.php b/module/productplan/ui/browsebylist.html.php index 80278fc6ff..9a507b2437 100644 --- a/module/productplan/ui/browsebylist.html.php +++ b/module/productplan/ui/browsebylist.html.php @@ -41,15 +41,25 @@ $cols = $this->loadModel('datatable')->getSetting('productplan'); $tableData = initTableData($plans, $cols, $this->productplan); foreach($tableData as $plan) { + $otherActions = array(); + foreach($plan->actions as $i => $action) + { + if(is_string($action) && strpos($action, 'other:') !== false) + { + $otherActions = explode(',', str_replace('other:', '', $action)); + break; + } + } + $otherActions = array_filter(array_map(function($action) use($plan) { - if($plan->status == 'doing' && (str_contains($action, 'close') || str_contains($action, 'activate'))) return $action; - if($plan->status == 'done' && str_contains($action, 'activate')) return $action; - if($plan->status == 'closed' && str_contains($action, 'close')) return $action; + if($plan->status == 'doing' && (strpos($action, 'close') !== false || strpos($action, 'activate') !== false)) return $action; + if($plan->status == 'done' && strpos($action, 'activate') !== false) return $action; + if($plan->status == 'closed' && strpos($action, 'close') !== false) return $action; if($plan->status == 'wait') return $action; return null; - }, explode(',', str_replace('other:', '', $plan->actions[1])))); - if($otherActions) $plan->actions[1] = 'other:' . implode(',', $otherActions); + }, $otherActions)); + if($otherActions) $plan->actions[$i] = 'other:' . implode(',', $otherActions); } $canBatchEdit = common::hasPriv('productplan', 'batchEdit'); diff --git a/module/productplan/ui/view.html.php b/module/productplan/ui/view.html.php index 1800abf0dc..0528b35443 100644 --- a/module/productplan/ui/view.html.php +++ b/module/productplan/ui/view.html.php @@ -189,9 +189,16 @@ detailBody ( setClass('tab-actions absolute right-0 gap-2'), setStyle('top', '-8px'), - dropdown + empty($createStoryLink) && empty($batchCreateStoryLink) ? null : dropdown ( - btn(set::text($lang->story->create), set::target('_parent'), setClass('secondary' . (empty($createStoryLink) ? ' disabled' : '')), set::icon('plus'), set::caret(true), set::url($createStoryLink)), + btn + ( + set::text($lang->story->create), + setClass('open-url secondary' . (empty($createStoryLink) ? ' disabled' : '')), + set::icon('plus'), + set::caret(true), + set::url($createStoryLink) + ), set::items(array(array('text' => $lang->story->batchCreate, 'url' => $batchCreateStoryLink, 'class' => empty($batchCreateStoryLink) ? 'disabled' : ''))), set::trigger('hover'), set::placement('bottom-end') diff --git a/module/program/tao.php b/module/program/tao.php index dc78fab5fa..c32c3c2445 100644 --- a/module/program/tao.php +++ b/module/program/tao.php @@ -271,10 +271,11 @@ class programTao extends programModel $summary = $this->setNoTaskExecution($projectIdList); /* 2. Get summary of executions to be refreshed. */ $tasks = $this->dao->select('t1.id, execution, t1.estimate, t1.consumed, t1.`left`, t1.status')->from(TABLE_TASK)->alias('t1') - ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project=t2.id') + ->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.execution = t2.id') ->where('t1.deleted')->eq(0) ->andWhere('t1.parent')->ge(0) // Ignore parent task. - ->beginIF(!empty($projectIdList))->andWhere('t1.project')->in($projectIdList)->fi() + ->andWhere('t2.deleted')->eq(0) + ->beginIF(!empty($projectIdList))->andWhere('t2.project')->in($projectIdList)->fi() ->fetchAll('id'); foreach($tasks as $task) diff --git a/module/program/ui/productview.html.php b/module/program/ui/productview.html.php index 4fa32cda6c..c726de1a55 100644 --- a/module/program/ui/productview.html.php +++ b/module/program/ui/productview.html.php @@ -289,7 +289,7 @@ dtable set::checkInfo(jsRaw("function(checkedIDList){ return window.footerSummary(checkedIDList, '{$pageSummary}');}")), set::emptyTip($lang->program->noProgram), set::createTip($lang->program->create), - set::createLink(hasPriv('product', 'create') ? createLink('product', 'create') : null) + set::createLink(hasPriv('program', 'create') ? createLink('program', 'create') : null) ); render(); diff --git a/module/program/zen.php b/module/program/zen.php index 4f88c0e174..54c88019b0 100644 --- a/module/program/zen.php +++ b/module/program/zen.php @@ -37,6 +37,8 @@ class programZen extends program protected function buildProgramForCreate(): object { $fields = $this->config->program->form->create; + if($this->post->longTime) $fields['end']['skipRequired'] = true; + $editorFields = array_keys(array_filter(array_map(function($config){return $config['control'] == 'editor';}, $fields))); foreach(explode(',', trim($this->config->program->create->requiredFields, ',')) as $field) { @@ -68,8 +70,10 @@ class programZen extends program */ protected function buildProgramForEdit(int $programID): object { - $oldProgram = $this->program->fetchByID($programID); - $fields = $this->config->program->form->edit; + $oldProgram = $this->program->fetchByID($programID); + $fields = $this->config->program->form->edit; + if($this->post->longTime) $fields['end']['skipRequired'] = true; + $editorFields = array_keys(array_filter(array_map(function($config){return $config['control'] == 'editor';}, $fields))); foreach(explode(',', trim($this->config->program->edit->requiredFields, ',')) as $field) { diff --git a/module/project/config/dtable.php b/module/project/config/dtable.php index 97bbf869b5..c791eceb35 100755 --- a/module/project/config/dtable.php +++ b/module/project/config/dtable.php @@ -18,7 +18,7 @@ $config->project->dtable->fieldList['name']['name'] = 'name'; $config->project->dtable->fieldList['name']['type'] = 'title'; $config->project->dtable->fieldList['name']['sortType'] = true; $config->project->dtable->fieldList['name']['link'] = array('module' => 'project', 'method' => 'index', 'params' => 'projectID={id}'); -$config->project->dtable->fieldList['name']['iconRender'] = 'RAWJSRAWJS'; +if($config->vision != 'lite') $config->project->dtable->fieldList['name']['iconRender'] = 'RAWJSRAWJS'; $config->project->dtable->fieldList['name']['group'] = 1; $config->project->dtable->fieldList['name']['required'] = true; @@ -57,7 +57,7 @@ $config->project->dtable->fieldList['PM']['group'] = 3; $config->project->dtable->fieldList['PM']['required'] = true; $config->project->dtable->fieldList['storyPoints']['title'] = $lang->project->storyPoints; -$config->project->dtable->fieldList['storyPoints']['type'] = 'number'; +$config->project->dtable->fieldList['storyPoints']['type'] = 'count'; $config->project->dtable->fieldList['storyPoints']['group'] = 4; $config->project->dtable->fieldList['storyPoints']['show'] = true; $config->project->dtable->fieldList['storyPoints']['width'] = '120px'; diff --git a/module/project/config/form.php b/module/project/config/form.php index 1d7848df71..ed47a35864 100755 --- a/module/project/config/form.php +++ b/module/project/config/form.php @@ -20,7 +20,7 @@ $config->project->form->create['PM'] = array('type' => 'string', 'requir $config->project->form->create['budget'] = array('type' => 'string', 'required' => false, 'default' => ''); $config->project->form->create['budgetUnit'] = array('type' => 'string', 'required' => false, 'default' => 'CNY'); $config->project->form->create['begin'] = array('type' => 'date', 'required' => true); -$config->project->form->create['end'] = array('type' => 'date', 'required' => true, 'default' => null); +$config->project->form->create['end'] = array('type' => 'date', 'required' => false, 'default' => null); $config->project->form->create['days'] = array('type' => 'int', 'required' => false, 'default' => 0); $config->project->form->create['desc'] = array('type' => 'string', 'required' => false, 'default' => '', 'control' => 'editor'); $config->project->form->create['acl'] = array('type' => 'string', 'required' => false, 'default' => ''); diff --git a/module/project/control.php b/module/project/control.php index 3446f80bd9..e217d1ba82 100755 --- a/module/project/control.php +++ b/module/project/control.php @@ -399,6 +399,8 @@ class project extends control if($_POST) { + if($this->post->longTime || $this->post->LONG_TIME) $this->config->project->form->create['end']['skipRequired'] = true; + $postData = form::data($this->config->project->form->create); $project = $this->projectZen->prepareCreateExtras($postData); if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); @@ -461,6 +463,8 @@ class project extends control if($_POST) { + if($this->post->longTime) $this->config->project->form->edit['end']['skipRequired'] = true; + $postData = form::data($this->config->project->form->edit); $postProductData = form::data($this->config->project->form->edit)->get('products,plans,branch'); $newProject = $this->projectZen->prepareProject($postData, $project->hasProduct); diff --git a/module/project/model.php b/module/project/model.php index 3accd13983..5f98862ebd 100755 --- a/module/project/model.php +++ b/module/project/model.php @@ -1239,10 +1239,7 @@ class projectModel extends model ->fetchPairs(); $this->loadModel('personnel')->updateWhitelist($whitelist, 'product', current($linkedProducts)); } - else - { - $this->loadModel('personnel')->updateWhitelist($whitelist, 'project', $projectID); - } + $this->loadModel('personnel')->updateWhitelist($whitelist, 'project', $projectID); } return !dao::isError(); diff --git a/module/project/tao.php b/module/project/tao.php index 2504431a34..2fabc72df1 100755 --- a/module/project/tao.php +++ b/module/project/tao.php @@ -1014,14 +1014,12 @@ class projectTao extends projectModel { unset($lang->project->menu->projectplan); unset($lang->project->menu->settings['subMenu']->module); - if(isset($lang->project->menu->storyGroup)) unset($lang->project->menu->storyGroup); - return true; } $projectProduct = (int)$this->dao->select('product')->from(TABLE_PROJECTPRODUCT)->where('project')->eq($projectID)->fetch('product'); if(isset($lang->project->menu->settings['subMenu']->module['link'])) $lang->project->menu->settings['subMenu']->module['link'] = sprintf($lang->project->menu->settings['subMenu']->module['link'], $projectProduct); - if(in_array($model, $this->config->project->scrumList)) $lang->project->menu->projectplan['link'] = sprintf($lang->project->menu->projectplan['link'], $projectProduct); + if(!$hasProduct && in_array($model, $this->config->project->scrumList)) $lang->project->menu->projectplan['link'] = sprintf($lang->project->menu->projectplan['link'], $projectProduct); /* Init story dropdown for project secondary meun. */ if($model !== 'kanban' && !empty($this->config->URAndSR) && isset($lang->project->menu->storyGroup)) @@ -1034,7 +1032,7 @@ class projectTao extends projectModel if(isset($this->app->params['storyType']) && $this->app->params['storyType'] == 'requirement') $lang->project->menu->story['dropMenu']->requirement['subModule'] .= ',projectstory,story'; } - unset($lang->project->menu->settings['subMenu']->products); + if(!$hasProduct) unset($lang->project->menu->settings['subMenu']->products); if(isset($lang->project->menu->storyGroup)) unset($lang->project->menu->storyGroup); if(!in_array($model, $this->config->project->scrumList)) unset($lang->project->menu->projectplan); return true; @@ -1054,7 +1052,7 @@ class projectTao extends projectModel { global $lang; /* Single execution and has no product project menu. */ - if(!$project->hasProduct && !$project->multiple && !empty($this->config->URAndSR) && isset($lang->$navGroup->menu->storyGroup)) + if(!$project->multiple && !empty($this->config->URAndSR) && isset($lang->$navGroup->menu->storyGroup)) { $lang->$navGroup->menu->story = $lang->$navGroup->menu->storyGroup; $lang->$navGroup->menu->story['link'] = sprintf($lang->$navGroup->menu->storyGroup['link'], '%s', $project->id); diff --git a/module/project/test/model/updatewhitelist.php b/module/project/test/model/updatewhitelist.php new file mode 100644 index 0000000000..db5eae9649 --- /dev/null +++ b/module/project/test/model/updatewhitelist.php @@ -0,0 +1,64 @@ +#!/usr/bin/env php +updateWhitelist(); +cid=1 + +- 测试敏捷项目添加白名单成员 @admin|user1|user2 +- 测试瀑布项目添加白名单成员 @admin|user1|user2 +- 测试看板项目添加白名单成员 @admin|user1|user2 +- 测试敏捷项目替换白名单成员 @user2 +- 测试瀑布项目替换白名单成员 @user2 +- 测试看板项目替换白名单成员 @user2 +- 测试敏捷项目删除白名单成员 @0 +- 测试瀑布项目删除白名单成员 @0 +- 测试看板项目删除白名单成员 @0 +- 测试项目型敏捷项目添加白名单成员 @admin|user1|user2 +- 测试项目型瀑布项目添加白名单成员 @admin|user1|user2 +- 测试项目型看板项目添加白名单成员 @admin|user1|user2 +- 测试项目型敏捷项目替换白名单成员 @user2 +- 测试项目型瀑布项目替换白名单成员 @user2 +- 测试项目型看板项目替换白名单成员 @user2 +- 测试项目型敏捷项目删除白名单成员 @0 +- 测试项目型瀑布项目删除白名单成员 @0 +- 测试项目型看板项目删除白名单成员 @0 + +*/ + +include dirname(__FILE__, 5) . '/test/lib/init.php'; +include dirname(__FILE__, 2) . '/project.class.php'; +su('admin'); + +$projectTable = zdTable('project')->config('project'); +$projectTable->whitelist->range('`admin,user1`, `user1,user2`'); +$projectTable->hasProduct->range('1{3},0{3}'); +$projectTable->gen(6); + +$projectproductTable = zdTable('projectproduct'); +$projectproductTable->project->range('1-6'); +$projectproductTable->product->range('1-6'); +$projectproductTable->gen(6); + +$projectIdList = range(1, 6); +$whitelistList = array('admin,user1,user2', 'user2', ''); + +$projectTester = new Project(); +r($projectTester->updateWhitelistTest($projectIdList[0], $whitelistList[0])) && p() && e('admin|user1|user2'); // 测试敏捷项目添加白名单成员 +r($projectTester->updateWhitelistTest($projectIdList[1], $whitelistList[0])) && p() && e('admin|user1|user2'); // 测试瀑布项目添加白名单成员 +r($projectTester->updateWhitelistTest($projectIdList[2], $whitelistList[0])) && p() && e('admin|user1|user2'); // 测试看板项目添加白名单成员 +r($projectTester->updateWhitelistTest($projectIdList[0], $whitelistList[1])) && p() && e('user2'); // 测试敏捷项目替换白名单成员 +r($projectTester->updateWhitelistTest($projectIdList[1], $whitelistList[1])) && p() && e('user2'); // 测试瀑布项目替换白名单成员 +r($projectTester->updateWhitelistTest($projectIdList[2], $whitelistList[1])) && p() && e('user2'); // 测试看板项目替换白名单成员 +r($projectTester->updateWhitelistTest($projectIdList[0], $whitelistList[2])) && p() && e('0'); // 测试敏捷项目删除白名单成员 +r($projectTester->updateWhitelistTest($projectIdList[1], $whitelistList[2])) && p() && e('0'); // 测试瀑布项目删除白名单成员 +r($projectTester->updateWhitelistTest($projectIdList[2], $whitelistList[2])) && p() && e('0'); // 测试看板项目删除白名单成员 +r($projectTester->updateWhitelistTest($projectIdList[3], $whitelistList[0])) && p() && e('admin|user1|user2'); // 测试项目型敏捷项目添加白名单成员 +r($projectTester->updateWhitelistTest($projectIdList[4], $whitelistList[0])) && p() && e('admin|user1|user2'); // 测试项目型瀑布项目添加白名单成员 +r($projectTester->updateWhitelistTest($projectIdList[5], $whitelistList[0])) && p() && e('admin|user1|user2'); // 测试项目型看板项目添加白名单成员 +r($projectTester->updateWhitelistTest($projectIdList[3], $whitelistList[1])) && p() && e('user2'); // 测试项目型敏捷项目替换白名单成员 +r($projectTester->updateWhitelistTest($projectIdList[4], $whitelistList[1])) && p() && e('user2'); // 测试项目型瀑布项目替换白名单成员 +r($projectTester->updateWhitelistTest($projectIdList[5], $whitelistList[1])) && p() && e('user2'); // 测试项目型看板项目替换白名单成员 +r($projectTester->updateWhitelistTest($projectIdList[3], $whitelistList[2])) && p() && e('0'); // 测试项目型敏捷项目删除白名单成员 +r($projectTester->updateWhitelistTest($projectIdList[4], $whitelistList[2])) && p() && e('0'); // 测试项目型瀑布项目删除白名单成员 +r($projectTester->updateWhitelistTest($projectIdList[5], $whitelistList[2])) && p() && e('0'); // 测试项目型看板项目删除白名单成员 diff --git a/module/project/test/project.class.php b/module/project/test/project.class.php index e71230d814..e1ca718dee 100644 --- a/module/project/test/project.class.php +++ b/module/project/test/project.class.php @@ -649,4 +649,25 @@ class Project return $this->project->dao->select('*')->from(TABLE_PROJECTPRODUCT)->where('project')->eq($projectID)->fetchAll(); } + + /** + * 更新此项目下或影子产品下的白名单列表。 + * Update whitelist by project. + * + * @param int $projectID + * @param string $whitelist + * @access public + * @return string + */ + public function updateWhitelistTest(int $projectID, string $whitelist): string + { + $project = new stdclass(); + $project->whitelist = $whitelist; + + $oldProject = $this->project->getByID($projectID); + $this->project->updateWhitelist($project, $oldProject); + if(dao::isError()) return ''; + $whitelist = $this->project->dao->select('whitelist')->from(TABLE_PROJECT)->where('id')->eq($projectID)->fetch('whitelist'); + return str_replace(',', '|', $whitelist); + } } diff --git a/module/project/ui/bug.html.php b/module/project/ui/bug.html.php index e959e51b59..9d81186f31 100644 --- a/module/project/ui/bug.html.php +++ b/module/project/ui/bug.html.php @@ -75,7 +75,10 @@ $footToolbar = array(); if($canBatchAssignTo) { $assignedToItems = array(); - foreach ($memberPairs as $key => $value) $assignedToItems[] = array('text' => $value, 'innerClass' => 'batch-btn ajax-btn', 'data-url' => createLink('bug', 'batchAssignTo', "assignedTo=$key&projectID={$project->id}&type=project")); + foreach ($memberPairs as $key => $value) + { + if(!empty($key)) $assignedToItems[] = array('text' => $value, 'innerClass' => 'batch-btn ajax-btn', 'data-url' => createLink('bug', 'batchAssignTo', "assignedTo=$key&projectID={$project->id}&type=project")); + } $footToolbar['items'][] = array('caret' => 'up', 'text' => $lang->bug->assignedTo, 'className' => 'btn btn-caret size-sm secondary', 'items' => $assignedToItems, 'type' => 'dropdown', 'data-placement' => 'top'); } @@ -88,6 +91,8 @@ dtable set::cols($cols), set::data(array_values($bugs)), set::userMap($users), + set::priList($lang->bug->priList), + set::severityList($lang->bug->severityList), set::customCols(true), set::checkable($canBatchAssignTo), set::canRowCheckable(jsRaw('function(rowID){return this.getRowInfo(rowID).data.canBeChanged;}')), diff --git a/module/project/ui/common.field.php b/module/project/ui/common.field.php index ad0953e9f6..b45889f903 100644 --- a/module/project/ui/common.field.php +++ b/module/project/ui/common.field.php @@ -48,7 +48,6 @@ unset($lang->project->endList[999]); $isLongTime = data('project.end') == LONG_TIME; $fields->field('begin') ->label($lang->project->planDate) - ->checkbox(array('text' => $lang->project->longTime, 'name' => 'longTime', 'checked' => $isLongTime)) ->required() ->controlBegin('dateRangePicker') ->beginName('begin') diff --git a/module/project/ui/create.field.php b/module/project/ui/create.field.php index 12b9bef928..1e8ff9213f 100644 --- a/module/project/ui/create.field.php +++ b/module/project/ui/create.field.php @@ -23,6 +23,7 @@ $fields->field('name') ->className($copyProject ? 'has-warning' : '') ->tip($copyProject ? $lang->project->copyProject->nameTips : null) ->value($copyProject ? data('copyProject.name') : ''); +if(!!$copyProject) $fields->field('multiple')->hidden(true)->value(data('copyProject.multiple')); if($hasCode) { @@ -36,6 +37,7 @@ if($hasCode) $fields->field('begin') ->tip($copyProject ? $lang->project->copyProject->endTips : null) ->tipClass('text-warning'); +if(!$copyProject || data('copyProject.multiple') != '0') $fields->field('begin')->checkbox(array('text' => $lang->project->longTime, 'name' => 'longTime', 'checked' => false)); $fields->field('days') ->control('input', array('className' => $copyProject ? 'has-warning' : '')) @@ -44,6 +46,7 @@ $fields->field('days') $fields->field('productsBox')->hidden(data('copyProject') && data('copyProject.hasProduct') == 0); + $fields->field('budget')->foldable(); $fields->field('acl') diff --git a/module/project/ui/create.html.php b/module/project/ui/create.html.php index aff2d6b15c..39b82137c8 100644 --- a/module/project/ui/create.html.php +++ b/module/project/ui/create.html.php @@ -49,11 +49,11 @@ $modeDropdown = dropdown ); $handleLongTimeChange = jsCallback()->do(<<<'JS' - const endPicker = $element.find('[name=end]').closest('[data-zui-datepicker]').zui('datePicker'); - const longTime = $element.find('[name=longTime]').prop('checked'); - endPicker.render({disabled: longTime}); - if(longTime) endPicker.$.setValue(''); - $element.find('[name=days]').attr('disabled', longTime ? 'disabled' : null); + const endPicker = $element.find('[name=end]').closest('[data-zui-datepicker]').zui('datePicker'); + const isLongTime = $element.find('[name=longTime]').prop('checked'); + endPicker.render({disabled: isLongTime}); + if(isLongTime) endPicker.$.setValue(''); + $element.find('[name=days]').attr('disabled', isLongTime ? 'disabled' : null); JS); $toggleLongTime = jsCallback()->do(<<<'JS' diff --git a/module/project/ui/edit.field.php b/module/project/ui/edit.field.php index 0ec275431c..4a3d90a39b 100644 --- a/module/project/ui/edit.field.php +++ b/module/project/ui/edit.field.php @@ -14,6 +14,8 @@ if(in_array($model, array('scrum', 'kanban'))) $fields->field('name')->checkbox( $fields->field('hasProduct')->disabled(true); +if(data('project.multiple') != '0') $fields->field('begin')->checkbox(array('text' => $lang->project->longTime, 'name' => 'longTime', 'checked' => data('project.end') == LONG_TIME)); + $budgetFuture = data('project.budget') !== null && !data('project.budget'); if(strpos($config->project->edit->requiredFields, 'budget') === false) $fields->field('budget')->checkbox(array('text' => $lang->project->future, 'name' => 'future', 'checked' => $budgetFuture)); $fields->field('budget')->value(data('project.budget') !== null && data('project.budget') == 0 ? '' : data('project.budget')); diff --git a/module/project/zen.php b/module/project/zen.php index 15e14550c6..12a47a5fe1 100755 --- a/module/project/zen.php +++ b/module/project/zen.php @@ -1029,7 +1029,8 @@ class projectZen extends project if(!$project->hasProduct) { $productID = $this->loadModel('product')->getProductIDByProject($project->id); - $this->project->deleteByTableName('zt_product', $productID); + $product = $this->product->getById($productID); + if($product->shadow) $this->project->deleteByTableName('zt_product', $productID); } } diff --git a/module/projectbuild/lang/de.php b/module/projectbuild/lang/de.php index fb300f9c67..8dad84b184 100644 --- a/module/projectbuild/lang/de.php +++ b/module/projectbuild/lang/de.php @@ -23,6 +23,6 @@ $lang->projectbuild->batchUnlink = 'Batch Unlink'; $lang->projectbuild->batchUnlinkStory = "Batch Unlink {$lang->SRCommon}"; $lang->projectbuild->batchUnlinkBug = 'Batch Unlink Bug'; $lang->projectbuild->name = 'Name'; -$lang->build->scmPath = 'SCM Path'; -$lang->build->filePath = 'File Path'; -$lang->build->desc = 'Description'; +$lang->projectbuild->scmPath = 'SCM Path'; +$lang->projectbuild->filePath = 'File Path'; +$lang->projectbuild->desc = 'Description'; diff --git a/module/projectbuild/lang/en.php b/module/projectbuild/lang/en.php index fb300f9c67..8dad84b184 100644 --- a/module/projectbuild/lang/en.php +++ b/module/projectbuild/lang/en.php @@ -23,6 +23,6 @@ $lang->projectbuild->batchUnlink = 'Batch Unlink'; $lang->projectbuild->batchUnlinkStory = "Batch Unlink {$lang->SRCommon}"; $lang->projectbuild->batchUnlinkBug = 'Batch Unlink Bug'; $lang->projectbuild->name = 'Name'; -$lang->build->scmPath = 'SCM Path'; -$lang->build->filePath = 'File Path'; -$lang->build->desc = 'Description'; +$lang->projectbuild->scmPath = 'SCM Path'; +$lang->projectbuild->filePath = 'File Path'; +$lang->projectbuild->desc = 'Description'; diff --git a/module/projectbuild/lang/fr.php b/module/projectbuild/lang/fr.php index fb300f9c67..8dad84b184 100644 --- a/module/projectbuild/lang/fr.php +++ b/module/projectbuild/lang/fr.php @@ -23,6 +23,6 @@ $lang->projectbuild->batchUnlink = 'Batch Unlink'; $lang->projectbuild->batchUnlinkStory = "Batch Unlink {$lang->SRCommon}"; $lang->projectbuild->batchUnlinkBug = 'Batch Unlink Bug'; $lang->projectbuild->name = 'Name'; -$lang->build->scmPath = 'SCM Path'; -$lang->build->filePath = 'File Path'; -$lang->build->desc = 'Description'; +$lang->projectbuild->scmPath = 'SCM Path'; +$lang->projectbuild->filePath = 'File Path'; +$lang->projectbuild->desc = 'Description'; diff --git a/module/projectrelease/test/tao/processrelease.php b/module/projectrelease/test/tao/processrelease.php index dba43e9a7e..560868417c 100644 --- a/module/projectrelease/test/tao/processrelease.php +++ b/module/projectrelease/test/tao/processrelease.php @@ -5,6 +5,7 @@ include dirname(__FILE__, 5) . '/test/lib/init.php'; include dirname(__FILE__, 2) . '/projectrelease.class.php'; zdTable('release')->gen(20); +zdTable('build')->gen(20); zdTable('product')->gen(20); zdTable('user')->gen(1); @@ -22,7 +23,7 @@ $releaseID = array(1, 3, 7, 8); $projectrelease = new projectreleaseTest(); -r($projectrelease->processReleaseTest($releaseID[0])) && p() && e('project:131 branch:0 build:1 branchName: buildInfos:版本1'); // 测试计算发布信息 1 -r($projectrelease->processReleaseTest($releaseID[1])) && p() && e('project:131 branch:0 build:3 branchName: buildInfos:版本3'); // 测试计算发布信息 3 -r($projectrelease->processReleaseTest($releaseID[2])) && p() && e('project:132 branch:2 build:7 branchName: buildInfos:新增项目版本发布'); // 测试计算发布信息 7 -r($projectrelease->processReleaseTest($releaseID[3])) && p() && e('project:0 branch:0 build:8 branchName: buildInfos:新增里程碑发布'); // 测试计算发布信息 8 +r($projectrelease->processReleaseTest($releaseID[0])) && p() && e('project:131 branch:0 build:1 branchName: buildInfos:项目11版本1'); // 测试计算发布信息 1 +r($projectrelease->processReleaseTest($releaseID[1])) && p() && e('project:131 branch:0 build:3 branchName: buildInfos:项目13版本3'); // 测试计算发布信息 3 +r($projectrelease->processReleaseTest($releaseID[2])) && p() && e('project:132 branch:2 build:7 branchName: buildInfos:项目17版本7'); // 测试计算发布信息 7 +r($projectrelease->processReleaseTest($releaseID[3])) && p() && e('project:0 branch:0 build:8 branchName: buildInfos:项目18版本8'); // 测试计算发布信息 8 diff --git a/module/projectstory/control.php b/module/projectstory/control.php index dfae5bb340..27acee8a2c 100644 --- a/module/projectstory/control.php +++ b/module/projectstory/control.php @@ -123,12 +123,13 @@ class projectStory extends control * @param string $orderBy * @param int $recPerPage * @param int $pageID + * @param string $storyType * @access public * @return void */ - public function linkStory($projectID = 0, $browseType = '', $param = 0, $orderBy = 'id_desc', $recPerPage = 50, $pageID = 1) + public function linkStory($projectID = 0, $browseType = '', $param = 0, $orderBy = 'id_desc', $recPerPage = 50, $pageID = 1, $storyType = 'story') { - echo $this->fetch('execution', 'linkStory', "projectID={$projectID}&browseType={$browseType}¶m={$param}&orderBy={$orderBy}&recPerPage={$recPerPage}&pageID={$pageID}"); + echo $this->fetch('execution', 'linkStory', "projectID={$projectID}&browseType={$browseType}¶m={$param}&orderBy={$orderBy}&recPerPage={$recPerPage}&pageID={$pageID}&extra=&storyType=$storyType"); } /** diff --git a/module/repo/control.php b/module/repo/control.php index c75d303532..a860b24550 100644 --- a/module/repo/control.php +++ b/module/repo/control.php @@ -417,7 +417,7 @@ class repo extends control $oldRevision = isset($this->post->revision[1]) ? $this->post->revision[1] : ''; $newRevision = isset($this->post->revision[0]) ? $this->post->revision[0] : ''; - $this->locate($this->repo->createLink('diff', "repoID=$repoID&objectID=$objectID&entry=" . $this->repo->encodePath($path) . "&oldrevision=$oldRevision&newRevision=$newRevision")); + return $this->locate($this->repo->createLink('diff', "repoID=$repoID&objectID=$objectID&entry=" . $this->repo->encodePath($path) . "&oldrevision=$oldRevision&newRevision=$newRevision")); } /* Set menu and session. */ @@ -427,11 +427,7 @@ class repo extends control /* Get repo and synchronous commit. */ $repo = $this->repo->getByID($repoID); if($repo->SCM == 'Git' && !is_dir($repo->path)) return $this->sendError(sprintf($this->lang->repo->error->notFound, $repo->name, $repo->path), $this->repo->createLink('maintain')); - if($repo && substr($repo->path, 0, 4) == 'http') - { - if(!$this->loadModel('admin')->checkInternet($repo->path)) return $this->sendError($this->lang->repo->error->connect, true); - } - + if($this->repoZen->checkRepoInternet($repo)) return $this->sendError($this->lang->repo->error->connect, true); if(!$repo->synced) return $this->locate($this->repo->createLink('showSyncCommit', "repoID=$repoID&objectID=$objectID")); /* Set branch or tag for git. */ @@ -478,7 +474,6 @@ class repo extends control $this->view->cloneUrl = $this->repo->getCloneUrl($repo); $this->view->repoPairs = $this->repo->getRepoPairs($this->app->tab, $objectID); $this->view->branchOrTag = $branchOrTag; - $this->display(); } diff --git a/module/repo/zen.php b/module/repo/zen.php index 3fca8b8932..5b1fb9457a 100644 --- a/module/repo/zen.php +++ b/module/repo/zen.php @@ -1578,4 +1578,23 @@ class repoZen extends repo return $tree; } + + /** + * 检查代码库是否能正常访问。 + * Check repo connected. + * + * @param object $repo + * @access protected + * @return bool + */ + protected function checkRepoInternet(object $repo): bool + { + if(!$repo) return false; + + $repoUrl = ''; + if(empty($repoUrl) && isset($repo->path) && substr($repo->path, 0, 4) == 'http') $repoUrl = $repo->path; + if(empty($repoUrl) && isset($repo->client) && substr($repo->client, 0, 4) == 'http') $repoUrl = $repo->client; + if(empty($repoUrl) && isset($repo->apiPath) && substr($repo->apiPath, 0, 4) == 'http') $repoUrl = $repo->apiPath; + return $repoUrl && !$this->loadModel('admin')->checkInternet($repoUrl); + } } diff --git a/module/report/test/model/createsinglejson.php b/module/report/test/model/createsinglejson.php index 0fb9d182c0..c7fc038da0 100755 --- a/module/report/test/model/createsinglejson.php +++ b/module/report/test/model/createsinglejson.php @@ -23,8 +23,8 @@ $report = new reportTest(); $executionID = array(101, 102, 103, 104, 105); -r($report->createSingleJSONTest($executionID[0])) && p() && e('0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2,2,2,2'); // 测试获取执行 101 的json数据 -r($report->createSingleJSONTest($executionID[1])) && p() && e('0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2,2,2'); // 测试获取执行 102 的json数据 -r($report->createSingleJSONTest($executionID[2])) && p() && e('0,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2,2,2,2'); // 测试获取执行 103 的json数据 -r($report->createSingleJSONTest($executionID[3])) && p() && e('0,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2,2,2,2'); // 测试获取执行 104 的json数据 -r($report->createSingleJSONTest($executionID[4])) && p() && e('0,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2,2,2'); // 测试获取执行 105 的json数据 +r($report->createSingleJSONTest($executionID[0])) && p() && e('0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2,2,2'); // 测试获取执行 101 的json数据 +r($report->createSingleJSONTest($executionID[1])) && p() && e('0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2,2,2'); // 测试获取执行 102 的json数据 +r($report->createSingleJSONTest($executionID[2])) && p() && e('0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2,2,2'); // 测试获取执行 103 的json数据 +r($report->createSingleJSONTest($executionID[3])) && p() && e('0,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2,2,2'); // 测试获取执行 104 的json数据 +r($report->createSingleJSONTest($executionID[4])) && p() && e('0,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2,2,2'); // 测试获取执行 105 的json数据 diff --git a/module/report/test/model/getuseryearcontributions.php b/module/report/test/model/getuseryearcontributions.php index 7101f9abb2..51441bb2a1 100755 --- a/module/report/test/model/getuseryearcontributions.php +++ b/module/report/test/model/getuseryearcontributions.php @@ -6,6 +6,7 @@ include dirname(__FILE__, 2) . '/report.class.php'; zdTable('action')->config('action_year')->gen('100'); zdTable('case')->gen('20'); +zdTable('doc')->gen('20'); zdTable('testrun')->gen('20'); zdTable('testresult')->gen('10'); zdTable('user')->gen(1); diff --git a/module/report/test/model/yaml/getusertasks/execution.yaml b/module/report/test/model/yaml/getusertasks/execution.yaml index 20491c95db..574cc5b7cc 100644 --- a/module/report/test/model/yaml/getusertasks/execution.yaml +++ b/module/report/test/model/yaml/getusertasks/execution.yaml @@ -13,7 +13,7 @@ fields: - field: name2 range: 1-10000 - field: project - range: 0{90},[11{5},60{12},61{6},100{5}]{100} + range: 0{90},11{5},60{12},61{6},100{5} - field: model range: scrum{30},[waterfall{30},kanban{30},[]{28}]{100} - field: attribute diff --git a/module/screen/test/model/getbarchartoption.php b/module/screen/test/model/getbarchartoption.php index 7dac1445a3..2fc94767dd 100755 --- a/module/screen/test/model/getbarchartoption.php +++ b/module/screen/test/model/getbarchartoption.php @@ -59,7 +59,7 @@ r(isset($component4->option->dataset->dimensions[0]) && $component4->option->dat list($component5, $chart5) = getComponetAndChart($screen, $filter5); $screen->getBarChartOption($component5, $chart5); $dataset = isset($component5) && $component5->option->dataset ? $component5->option->dataset : null; -r($dataset && $dataset->dimensions[0] == '年份' && count($dataset->source) == 2) && p('') && e(1); //测试type为stackedBar的图表是否显示正确,生成的指标项和数据项是否正确。 +r($dataset && $dataset->dimensions[0] == '年份' && count($dataset->source) >= 1) && p('') && e(1); //测试type为stackedBar的图表是否显示正确,生成的指标项和数据项是否正确。 list($component6, $chart6) = getComponetAndChart($screen, $filter6); -r(is_null($component6) && is_null($chart6)) && p('') && e(1); //测试type为bar的图表是否显示正确,由于目前系统里没有这种类型的图表,故不作展示。 \ No newline at end of file +r(is_null($component6) && is_null($chart6)) && p('') && e(1); //测试type为bar的图表是否显示正确,由于目前系统里没有这种类型的图表,故不作展示。 diff --git a/module/screen/test/model/getchartoption.php b/module/screen/test/model/getchartoption.php index d34e49974b..5963845b99 100644 --- a/module/screen/test/model/getchartoption.php +++ b/module/screen/test/model/getchartoption.php @@ -85,7 +85,7 @@ r( list($component5, $chart5) = getComponetAndChart($screen, $filter5); $screen->getChartOptionTest($chart5, $component5); $dataset = isset($component5) && $component5->option->dataset ? $component5->option->dataset : null; -r($dataset && $dataset->dimensions[0] == '年份' && count($dataset->source) == 2) && p('') && e(1); // 测试type为stackedBar的图表是否显示正确,生成的指标项和数据项是否正确。 +r($dataset && $dataset->dimensions[0] == '年份' && count($dataset->source) >= 1) && p('') && e(1); // 测试type为stackedBar的图表是否显示正确,生成的指标项和数据项是否正确。 list($component6, $chart6) = getComponetAndChart($screen, $filter6); r(is_null($component6) && is_null($chart6)) && p('') && e(1); //测试type为bar的图表是否显示正确,由于目前系统里没有这种类型的图表,故不作展示。 @@ -93,7 +93,7 @@ r(is_null($component6) && is_null($chart6)) && p('') && e(1); list($component7, $chart7) = getComponetAndChart($screen, $filter7); $screen->getChartOptionTest($chart7, $component7); r( - isset($component7->option->series[0]->data[0]->value[0]) + isset($component7->option->series[0]->data[0]->value[0]) && isset($component7->option->dataset) && $component7->option->dataset === $component7->option->series[0]->data[0]->value[0] ) && p('') && e(1); // 测试type为piecircle的图表是否显示正确,生成的指标项和数据项是否正确。 @@ -122,6 +122,6 @@ r($component11->option->dataset == "0.000") && p('') && e(1); list($component12, $chart12) = getComponetAndChart($screen, $filter11); r(is_null($component12) && is_null($chart12)) && p('') && e(1); //测试type为radar的图表是否显示正确,由于目前系统里没有这种类型的图表,故不作展示。 -list($component13, $chart13) = getComponetAndChart($screen, $filter12); +list($component13, $chart13) = getComponetAndChart($screen, $filter12); $screen->getChartOptionTest($chart13, $component13); -r($component13->option->dataset === '0') && p('') && e(1); //测试type为card的图表是否显示正确,生成的指标项和数据项是否正确。 \ No newline at end of file +r($component13->option->dataset === '0') && p('') && e(1); //测试type为card的图表是否显示正确,生成的指标项和数据项是否正确。 diff --git a/module/search/config.php b/module/search/config.php index 2864123cbb..0be591e21a 100644 --- a/module/search/config.php +++ b/module/search/config.php @@ -149,7 +149,7 @@ $config->search->fields->aiapp = new stdclass(); $config->search->fields->aiapp->id = 'id'; $config->search->fields->aiapp->title = 'name'; $config->search->fields->aiapp->content = 'desc'; -$config->search->fields->aiapp->addedDate = 'createdBy'; +$config->search->fields->aiapp->addedDate = 'createdDate'; $config->search->fields->aiapp->editedDate = 'editedDate'; /* Set the recPerPage of search. */ diff --git a/module/search/test/tao/processdatalist.php b/module/search/test/tao/processdatalist.php index 1ae067c5fa..004a91dd75 100755 --- a/module/search/test/tao/processdatalist.php +++ b/module/search/test/tao/processdatalist.php @@ -25,7 +25,7 @@ $action->objectType->range('bug'); $action->objectID->range('1,2'); $action->actor->range('admin'); $action->action->range('opened'); -$action->date->range('20230102 000000:0')->type('timestamp')->format('YYYY-MM-DD hh::mm::ss'); +$action->date->range('20230102 000000:0')->type('timestamp')->format('YYYY-MM-DD hh:mm:ss'); $action->comment->prefix('创建bug')->range('1,2'); $action->gen(2); @@ -58,4 +58,4 @@ $dataIdList = array(1, 2); $search = new searchTest(); r($search->processDataListTest($modules[0], $bugField, $dataIdList)) && p('1:comment') && e("创建bug1文件标题1.txt"); //测试处理数据的comment -r($search->processDataListTest($modules[1], $caseField, $dataIdList)) && p('1:desc,expect') && e("用例步骤描述1,这是用例预期结果1"); //测试处理用例的步骤 \ No newline at end of file +r($search->processDataListTest($modules[1], $caseField, $dataIdList)) && p('1:desc,expect') && e("用例步骤描述1,这是用例预期结果1"); //测试处理用例的步骤 diff --git a/module/story/config/dtable.php b/module/story/config/dtable.php index 5bd877509e..02217036c2 100644 --- a/module/story/config/dtable.php +++ b/module/story/config/dtable.php @@ -338,7 +338,7 @@ $config->story->dtable->fieldList['actions']['actionsMap']['storyEstimate']['dat $config->story->dtable->fieldList['actions']['actionsMap']['storyEstimate']['data-id'] = 'storyEstimateModal'; $config->story->dtable->fieldList['actions']['actionsMap']['unlink']['icon'] = 'unlink'; -$config->story->dtable->fieldList['actions']['actionsMap']['unlink']['hint'] = $lang->execution->unlinkStory; +$config->story->dtable->fieldList['actions']['actionsMap']['unlink']['hint'] = $lang->story->unlink; $config->story->taskTable = new stdclass(); $config->story->taskTable->fieldList['id']['name'] = 'id'; diff --git a/module/story/control.php b/module/story/control.php index cff7383f93..c0a07a39fe 100755 --- a/module/story/control.php +++ b/module/story/control.php @@ -244,10 +244,21 @@ class story extends control $this->product->setMenu($story->product, $story->branch); } + $product = $this->product->getByID($story->product); + + if($product->shadow) + { + $products = $this->product->getPairs('', 0, '', 'all'); + } + else + { + $products = $this->product->getPairs(); + } + /* Assign. */ - $this->view->product = $this->product->getByID($story->product); + $this->view->product = $product; $this->view->productID = $this->view->product->id; - $this->view->products = $this->product->getPairs(); + $this->view->products = $products; $this->view->story = $story; $this->view->moduleOptionMenu = $this->tree->getOptionMenu($story->product, 'story', 0, (string)$story->branch); $this->view->plans = $this->loadModel('productplan')->getPairs($story->product, 0, '', true); @@ -1678,10 +1689,12 @@ class story extends control { $this->loadModel('project')->setMenu($projectID); if($project and $project->model == 'waterfall') unset($this->lang->story->report->charts['storiesPerPlan']); + $this->view->projectID = $projectID; } else { $this->loadModel('execution')->setMenu($projectID); + $this->view->executionID = $projectID; } if(!$project->hasProduct) diff --git a/module/story/model.php b/module/story/model.php index 9cff51e5e2..8763ed5bb1 100644 --- a/module/story/model.php +++ b/module/story/model.php @@ -344,10 +344,11 @@ class storyModel extends model * @param array|string|int $moduleIdList * @param string $type full|short * @param string $status all|noclosed|changing|active|draft|closed|reviewing + * @param string $storyType story|requirement * @access public * @return array */ - public function getExecutionStoryPairs(int $executionID = 0, int $productID = 0, string|int $branch = 'all', array|string|int $moduleIdList = '', string $type = 'full', string $status = 'all'): array + public function getExecutionStoryPairs(int $executionID = 0, int $productID = 0, string|int $branch = 'all', array|string|int $moduleIdList = '', string $type = 'full', string $status = 'all', string $storyType = 'story'): array { if(commonModel::isTutorialMode()) return $this->loadModel('tutorial')->getExecutionStoryPairs(); @@ -357,7 +358,7 @@ class storyModel extends model ->leftJoin(TABLE_PRODUCT)->alias('t3')->on('t1.product = t3.id') ->where('t1.project')->eq($executionID) ->andWhere('t2.deleted')->eq('0') - ->andWhere('t2.type')->eq('story') + ->andWhere('t2.type')->eq($storyType) ->beginIF($productID)->andWhere('t2.product')->eq($productID)->fi() ->beginIF($branch !== 'all')->andWhere('t2.branch')->in("0,$branch")->fi() ->beginIF($moduleIdList)->andWhere('t2.module')->in($moduleIdList)->fi() diff --git a/module/story/tao.php b/module/story/tao.php index d3641bc216..10c9dd6054 100644 --- a/module/story/tao.php +++ b/module/story/tao.php @@ -300,6 +300,15 @@ class storyTao extends storyModel $relationGroups = $this->batchGetRelations(array_keys($stories), 'requirement', array('*')); if(empty($stories)); + if($this->app->rawModule == 'projectstory') + { + $projectID = $this->session->project; + /* For story children. */ + $relationIDList = array(); + foreach($relationGroups as $relationGroup) $relationIDList = array_merge($relationIDList, array_keys($relationGroup)); + $projectIDList = $this->dao->select('story,project')->from(TABLE_PROJECTSTORY)->where('story')->in($relationIDList)->andWhere('project')->eq($projectID)->fetchPairs('story', 'project'); + } + foreach($stories as $story) { /* Merge subdivided stories for requirement. */ @@ -311,7 +320,8 @@ class storyTao extends storyModel if(empty($SRStory)) continue; $children = clone $SRStory; - $children->parent = $story->id; + $children->parent = $story->id; + $children->project = isset($projectIDList[$SRID]) ? $projectIDList[$SRID] : 0; $story->children[$SRID] = $children; } $story->linkStories = implode(',', array_column($story->children, 'title')); @@ -1115,10 +1125,10 @@ class storyTao extends storyModel { if($this->config->vision == 'lite') return true; - if(!in_array($story->status, array('launched', 'developing', 'active'))) return false; - if(!$isShadowProduct && $story->stage != 'wait') return false; - if($isShadowProduct && $story->stage != 'projected') return false; - if($story->parent > 0) return false; + if(in_array($story->status, array('launched', 'developing', 'active'))) return true; + if(!$isShadowProduct && $story->type == 'story' && $story->stage != 'wait') return false; + if($isShadowProduct && $story->stage != 'projected') return false; + if($story->parent > 0) return false; return true; } @@ -1442,14 +1452,15 @@ class storyTao extends storyModel $this->app->loadLang('task'); $this->config->story->affect = new stdclass(); $this->config->story->affect->projects = new stdclass(); - $this->config->story->affect->projects->fields[] = array('name' => 'id', 'title' => $this->lang->task->id); - $this->config->story->affect->projects->fields[] = array('name' => 'name', 'title' => $this->lang->task->name, 'link' => helper::createLink('task', 'view', 'id={id}')); - $this->config->story->affect->projects->fields[] = array('name' => 'assignedTo', 'title' => $this->lang->task->assignedTo); - $this->config->story->affect->projects->fields[] = array('name' => 'consumed', 'title' => $this->lang->task->consumed); - $this->config->story->affect->projects->fields[] = array('name' => 'left', 'title' => $this->lang->task->left); + $this->config->story->affect->projects->fields['id'] = array('name' => 'id', 'title' => $this->lang->task->id); + $this->config->story->affect->projects->fields['name'] = array('name' => 'name', 'title' => $this->lang->task->name, 'link' => helper::createLink('task', 'view', 'id={id}')); + $this->config->story->affect->projects->fields['assignedTo'] = array('name' => 'assignedTo', 'title' => $this->lang->task->assignedTo); + $this->config->story->affect->projects->fields['consumed'] = array('name' => 'consumed', 'title' => $this->lang->task->consumed); + $this->config->story->affect->projects->fields['left'] = array('name' => 'left', 'title' => $this->lang->task->left); if(empty($story->executions)) return $story; - foreach($story->executions as $executionID => $execution) if($execution->status == 'done') unset($story->executions[$executionID]); + $storyExecutions = $story->executions; + foreach($storyExecutions as $executionID => $execution) if($execution->status == 'done') unset($story->executions[$executionID]); $story->teams = $this->dao->select('account, root')->from(TABLE_TEAM)->where('root')->in(array_keys($story->executions))->andWhere('type')->eq('execution')->fetchGroup('root'); foreach($story->tasks as $executionTasks) @@ -1458,6 +1469,11 @@ class storyTao extends storyModel { $task->status = $this->processStatus('task', $task); $task->assignedTo = zget($users, $task->assignedTo); + if(isset($storyExecutions[$task->execution])) + { + $taskExecution = $storyExecutions[$task->execution]; + if(!$taskExecution->multiple) $this->config->story->affect->projects->fields['name']['link'] .= '#app=project'; + } } } return $story; diff --git a/module/story/test/model/update.php b/module/story/test/model/update.php index 7f2b02ca65..d49a80112f 100755 --- a/module/story/test/model/update.php +++ b/module/story/test/model/update.php @@ -21,16 +21,17 @@ pid=1 $story = new storyTest(); $story1 = $tester->loadModel('story')->fetchByID(2); -$story1->parent = 2; -$story1->pri = 4; -$story1->plan = '0'; -$story1->estimate = 1; -$story1->sourceNote = '测试来源备注1'; -$story1->product = 2; -$story1->linkStories = ''; -$story1->spec = ''; -$story1->verify = ''; -$story1->deleteFiles = array(); +$story1->parent = 2; +$story1->pri = 4; +$story1->plan = '0'; +$story1->estimate = 1; +$story1->sourceNote = '测试来源备注1'; +$story1->product = 2; +$story1->linkStories = ''; +$story1->spec = ''; +$story1->verify = ''; +$story1->retractedDate = NULL; +$story1->deleteFiles = array(); unset($story1->approvedDate); unset($story1->lastEditedDate); unset($story1->changedDate); diff --git a/module/story/ui/edit.html.php b/module/story/ui/edit.html.php index bcf654df29..6eab4c504c 100644 --- a/module/story/ui/edit.html.php +++ b/module/story/ui/edit.html.php @@ -194,7 +194,7 @@ detailBody ( setClass('mt-5'), set::title($lang->story->legendBasicInfo), - $story->parent <= 0 ? item + $story->parent <= 0 && !$product->shadow ? item ( set::trClass(zget($fields['product'], 'className', '')), set::name($lang->story->product), diff --git a/module/story/ui/report.html.php b/module/story/ui/report.html.php index 045a84dc46..31cfb1ba0c 100644 --- a/module/story/ui/report.html.php +++ b/module/story/ui/report.html.php @@ -10,6 +10,7 @@ declare(strict_types=1); */ namespace zin; +data('activeMenuID', $storyType); jsVar('params', "productID={$productID}&branchID={$branchID}&storyType={$storyType}&browseType={$browseType}&moduleID={$moduleID}"); jsVar('projectID', $projectID); jsVar('storyType', $storyType); diff --git a/module/story/ui/review.html.php b/module/story/ui/review.html.php index cbaf46e3d8..2745e2ec8c 100644 --- a/module/story/ui/review.html.php +++ b/module/story/ui/review.html.php @@ -49,7 +49,7 @@ foreach($fields as $field => $attr) ) ); } -if($this->config->vision != 'or') $formItems['affected'] = $getAffectedTabs($story, $users); +if(strpos('or,lite', $this->config->vision) === false) $formItems['affected'] = $getAffectedTabs($story, $users); modalHeader(); panel diff --git a/module/story/zen.php b/module/story/zen.php index ff1ff0a432..423d266da2 100644 --- a/module/story/zen.php +++ b/module/story/zen.php @@ -508,6 +508,7 @@ class storyZen extends story if(isset($attr['options']) and $attr['options'] == 'users') $fields[$field]['options'] = $users; if(!isset($fields[$field]['name'])) $fields[$field]['name'] = $field; if(!isset($fields[$field]['title'])) $fields[$field]['title'] = zget($this->lang->story, $field); + if(strpos(",{$this->config->story->create->requiredFields},", ",$field,") !== false) $fields[$field]['required'] = true; } /* 设置下拉菜单内容。 */ @@ -1744,17 +1745,9 @@ class storyZen extends story if($from != 'execution') return helper::createLink('story', 'view', "storyID={$storyID}&version=0¶m=0&storyType={$storyType}"); - $execution = $this->execution->getByID($this->session->execution); - - $module = 'story'; - $method = 'view'; - $params = "storyID=$storyID&version=0¶m={$this->session->execution}&storyType=$storyType"; - if($execution->multiple) - { - $module = 'execution'; - $method = 'storyView'; - $params = "storyID=$storyID"; - } + $module = 'execution'; + $method = 'storyView'; + $params = "storyID=$storyID"; return helper::createLink($module, $method, $params); } diff --git a/module/task/config/form.php b/module/task/config/form.php index 000709c0c4..824c8533f2 100644 --- a/module/task/config/form.php +++ b/module/task/config/form.php @@ -169,6 +169,7 @@ $config->task->form->finish['lastEditedDate'] = array('type' => 'datetime', 're $config->task->form->finish['assignedDate'] = array('type' => 'string', 'required' => false, 'default' => $now); $config->task->form->finish['finishedBy'] = array('type' => 'string', 'required' => false, 'default' => $app->user->account); $config->task->form->finish['lastEditedBy'] = array('type' => 'string', 'required' => false, 'default' => $app->user->account); +$config->task->form->finish['lastEditedDate'] = array('type' => 'datetime', 'required' => false, 'default' => $now); $config->task->form->close = array(); $config->task->form->close['status'] = array('type' => 'string', 'required' => false, 'default' => 'closed'); @@ -200,4 +201,4 @@ $config->task->form->editEffort = array(); $config->task->form->editEffort['date'] = array('type' => 'date', 'required' => true, 'default' => ''); $config->task->form->editEffort['work'] = array('type' => 'string', 'required' => false, 'default' => ''); $config->task->form->editEffort['consumed'] = array('type' => 'float', 'required' => true, 'default' => ''); -$config->task->form->editEffort['left'] = array('type' => 'float', 'required' => true, 'default' => ''); +$config->task->form->editEffort['left'] = array('type' => 'float', 'required' => false, 'default' => ''); diff --git a/module/task/js/editeffort.ui.js b/module/task/js/editeffort.ui.js new file mode 100644 index 0000000000..61e93082ad --- /dev/null +++ b/module/task/js/editeffort.ui.js @@ -0,0 +1,14 @@ +window.clickSubmit = function() +{ + const isEmpty = !$('#editEffortForm [name=date]').val() || !$('#editEffortForm [name=consumed]').val(); + const left = $('#editEffortForm [name=left]').val(); + if(!isEmpty && !isReadonly && (left == 0 || !left)) + { + const formUrl = $('#editEffortForm form').attr('action'); + const formData = new FormData($("#editEffortForm form")[0]); + zui.Modal.confirm(finishTaskTip).then((res) => { + if(res) $.ajaxSubmit({url: formUrl, data: formData}) + }); + return false; + } +} diff --git a/module/task/tao.php b/module/task/tao.php index 0f4cf8118f..d70a156e98 100644 --- a/module/task/tao.php +++ b/module/task/tao.php @@ -314,40 +314,9 @@ class taskTao extends taskModel */ protected function computeTaskProgress(object $task): float { - if($task->parent >= 0) - { - if($task->left > 0) return round($task->consumed / ($task->consumed + (float)$task->left), 2) * 100; - if($task->consumed == 0) return 0; - return 100; - } - else - { - /* 如果一个任务创建了多个子任务但都没有设置预计剩余工时,此时如果其中一个子任务已完成但其他子任务都未开始,会出现父任务进度100%但任务状态仍在进行中的情况,为了避免这种情况,父任务类型的任务进度需要单独计算。*/ - $left = 0; - $consumed = 0; - $childrenStatus = array(); - - /* 有的任务对象不是通过taskModel::getByID获取的(比如执行的任务列表),因此可能没有children,需要重新获取它的子任务。*/ - if(!isset($task->children)) - { - $task = clone $task; - $task->children = $this->dao->select('*')->from(TABLE_TASK)->where('parent')->eq($task->id)->fetchAll('id'); - } - - foreach($task->children as $childTask) - { - $left += $childTask->left; - $consumed += $childTask->consumed; - $childrenStatus[] = $childTask->status; - } - if($left != 0) return round($consumed / ($consumed + $left), 2) * 100; - if($consumed == 0) return 0; - - /* Compute progress by the count of done status in status list if some child task is finished(left is 0, consumed is not 0) but other task is in wait. */ - $statusCount = array_count_values($childrenStatus); - if(in_array('wait', $childrenStatus)) return empty($statusCount['done']) ? 0 : round($statusCount['done'] / count($childrenStatus), 2) * 100; - return 100; - } + if($task->left > 0) return round($task->consumed / ($task->consumed + (float)$task->left), 2) * 100; + if($task->consumed == 0) return 0; + return 100; } /** diff --git a/module/task/ui/create.field.php b/module/task/ui/create.field.php index bd1b79cac6..53d7cf45cf 100644 --- a/module/task/ui/create.field.php +++ b/module/task/ui/create.field.php @@ -118,6 +118,7 @@ $buildStoryBox = function($props) set::href(createLink('execution', 'linkStory', "executionID={$executionID}")), setClass('text-primary'), isInModal() ? setData('toggle', 'modal') : null, + isInModal() ? setData('size', 'lg') : null, data('lang.execution.linkStory') ) ); diff --git a/module/task/ui/edit.html.php b/module/task/ui/edit.html.php index 372f44fec5..56c0155319 100644 --- a/module/task/ui/edit.html.php +++ b/module/task/ui/edit.html.php @@ -213,7 +213,7 @@ detailBody set::value($task->parent), set::items($tasks) ) - ) : null, + ) : formHidden('parent', $task->parent), empty($modeText) ? item ( set::name($lang->task->mode), diff --git a/module/task/ui/editeffort.html.php b/module/task/ui/editeffort.html.php index 4843a2eb76..8c2ca2a954 100644 --- a/module/task/ui/editeffort.html.php +++ b/module/task/ui/editeffort.html.php @@ -12,11 +12,15 @@ declare(strict_types=1); namespace zin; $readonly = !$effort->isLast || (!empty($task->team) && $effort->left == 0); +jsVar('isReadonly', $readonly); +jsVar('finishTaskTip', $lang->task->confirmRecord); modalHeader(set::title($lang->task->editEffort)); formPanel ( + setID('editEffortForm'), set::shadow(!isAjaxRequest('modal')), + set::ajax(array('beforeSubmit' => jsRaw("clickSubmit"))), formGroup ( set::width('1/3'), diff --git a/module/testcase/control.php b/module/testcase/control.php index be085760a6..37ee8baab1 100644 --- a/module/testcase/control.php +++ b/module/testcase/control.php @@ -334,7 +334,7 @@ class testcase extends control $this->view->title = $this->products[$productID] . $this->lang->colon . $this->lang->testcase->batchCreate; $this->view->productID = $productID; $this->view->productName = $this->products[$productID]; - $this->view->moduleOptionMenu = $this->tree->getOptionMenu($productID, 'case', 0, $branch === 'all' ? 0 : $branch); + $this->view->moduleOptionMenu = $this->tree->getOptionMenu($productID, 'case', 0, $branch === 'all' ? '0' : $branch); $this->view->currentSceneID = 0; $this->view->branch = $branch; $this->view->needReview = $this->testcase->forceNotReview() == true ? 0 : 1; @@ -1198,7 +1198,7 @@ class testcase extends control if($_POST) { - $cases = $this->testcaseZen->buildCasesForShowImport(); + $cases = $this->testcaseZen->buildCasesForShowImport($productID); $cases = $this->testcaseZen->checkCasesForShowImport($cases); if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); diff --git a/module/testcase/js/common.ui.js b/module/testcase/js/common.ui.js index 63bf44430d..8b438bb857 100644 --- a/module/testcase/js/common.ui.js +++ b/module/testcase/js/common.ui.js @@ -19,9 +19,8 @@ window.loadProductStories = function(productID) window.loadProductBranches = function(productID) { - var param = config.currentMethod == 'create' ? 'active' : 'all'; - var oldBranch = config.currentMethod == 'edit' ? caseBranch : 0; - var param = 'productID=' + productID + '&oldBranch=' + oldBranch + '¶m=' + param; + const oldBranch = config.currentMethod == 'edit' ? caseBranch : 0; + let param = 'productID=' + productID + '&oldBranch=' + oldBranch + '¶m=' + config.currentMethod == 'create' ? 'active' : 'all'; if(typeof(tab) != 'undefined' && (tab == 'execution' || tab == 'project')) param += '&projectID=' + objectID; $.get($.createLink('branch', 'ajaxGetBranches', param), function(data) @@ -122,7 +121,7 @@ window.loadScenesForBatch = function(productID, moduleID, $currentRow) let branchID = $currentRow.find('.form-batch-control[data-name="branch"] .pick-value').val(); if(!branchID) branchID = 0; - let sceneLink = $.createLink('testcase', 'ajaxGetScenes', 'productID=' + productID + '&branch=' + branchID + '&moduleID=' + moduleID); + const sceneLink = $.createLink('testcase', 'ajaxGetScenes', 'productID=' + productID + '&branch=' + branchID + '&moduleID=' + moduleID); $.getJSON(sceneLink, function(scenes) { let $row = $currentRow; @@ -149,10 +148,10 @@ window.loadScenesForBatch = function(productID, moduleID, $currentRow) */ window.loadStoriesForBatch = function(productID, moduleID, num, $currentRow = null) { - const branchID = $currentRow.find('.form-batch-control[data-name="branch"]').length ? $currentRow.find('.form-batch-control[data-name="branch"] .pick-value').val() : 0; + let branchID = $currentRow.find('.form-batch-control[data-name="branch"]').length ? $currentRow.find('.form-batch-control[data-name="branch"] .pick-value').val() : 0; if(!branchID) branchID = 0; - var storyLink = $.createLink('story', 'ajaxGetProductStories', 'productID=' + productID + '&branch=' + branchID + '&moduleID=' + moduleID + '&storyID=0&onlyOption=false&status=noclosed&limit=0&type=full&hasParent=1&objectID=0&number=' + num); + const storyLink = $.createLink('story', 'ajaxGetProductStories', 'productID=' + productID + '&branch=' + branchID + '&moduleID=' + moduleID + '&storyID=0&onlyOption=false&status=noclosed&limit=0&type=full&hasParent=1&objectID=0&number=' + num); $.getJSON(storyLink, function(stories) { if(!stories) return; diff --git a/module/testcase/js/createscene.ui.js b/module/testcase/js/createscene.ui.js index 3fb1415d72..e9670cf5a4 100644 --- a/module/testcase/js/createscene.ui.js +++ b/module/testcase/js/createscene.ui.js @@ -27,4 +27,5 @@ function refreshModules() window.renderModulePicker = function() { refreshModules(); + $('#module').parent().find('.input-group-btn').hide(); } diff --git a/module/testcase/js/showimport.ui.js b/module/testcase/js/showimport.ui.js index 65e5e7241e..daad0fa3c5 100644 --- a/module/testcase/js/showimport.ui.js +++ b/module/testcase/js/showimport.ui.js @@ -2,8 +2,8 @@ window.handleRenderRow = function($row, index, row) { if(!row) return false; - row.id = index + 1; - let stepDesc = ""; + if(row.rawID == '') row.rawID = index + 1; + let stepDesc = ""; let stepExpect = ''; if(!stepData[index]) return false; @@ -15,14 +15,14 @@ window.handleRenderRow = function($row, index, row) $.each(descs, function(id, desc) { if(!desc.content) return; - stepDesc += "
" + desc.number + "、
"; - stepExpect += "
"; + stepDesc += "
" + desc.number + "、
"; + stepExpect += "
"; }) } else { - stepDesc += "
1、
"; - stepExpect += "
"; + stepDesc += "
1、
"; + stepExpect += "
"; } diff --git a/module/testcase/js/showxmindimport.ui.js b/module/testcase/js/showxmindimport.ui.js index 9ef3eda558..7ff6d5870b 100644 --- a/module/testcase/js/showxmindimport.ui.js +++ b/module/testcase/js/showxmindimport.ui.js @@ -37,7 +37,7 @@ $(document).ready(function() var data = mindmapMgr.toJson(); if(data.testcaseList.length == 0) { - alert(jsLng.caseNotExist); + zui.Modal.alert(jsLng.caseNotExist); return; } @@ -48,7 +48,7 @@ $(document).ready(function() form.append("sceneList", sceneList); form.append("testcaseList", testcaseList); - postAndLoadPage($.createLink('testcase', 'saveXmindImport'), form); + $.ajaxSubmit({url: $.createLink('testcase', 'saveXmindImport'), data: form}); }) }); diff --git a/module/testcase/model.php b/module/testcase/model.php index 702f2e2794..27bdef1761 100755 --- a/module/testcase/model.php +++ b/module/testcase/model.php @@ -597,7 +597,7 @@ class testcaseModel extends model $this->testcaseTao->updateCase2Project($oldCase, $case); - if($case->stepChanged) $this->testcaseTao->updateStep($case, $oldCase); + if(!empty($case->stepChanged)) $this->testcaseTao->updateStep($case, $oldCase); if($oldCase->lib && empty($oldCase->product)) { @@ -613,7 +613,7 @@ class testcaseModel extends model $this->loadModel('file')->processFile4Object('testcase', $oldCase, $case); /* Join the steps to diff. */ - if($case->stepChanged && $case->steps) + if(!empty($case->stepChanged) && $case->steps) { $oldCase->steps = $this->joinStep($oldCase->steps); $case->steps = $this->joinStep($this->getByID($oldCase->id, $case->version)->steps); diff --git a/module/testcase/tao.php b/module/testcase/tao.php index ff7c59ef33..44112e4e1a 100644 --- a/module/testcase/tao.php +++ b/module/testcase/tao.php @@ -321,7 +321,12 @@ class testcaseTao extends testcaseModel */ protected function getRelatedSteps(array $caseIdList): array { - return $this->dao->select('id, parent, `case`, version, type, `desc`, expect')->from(TABLE_CASESTEP)->where('`case`')->in($caseIdList)->orderBy('version desc,id')->fetchGroup('case', 'id'); + return $this->dao->select('DISTINCT t2.id, t2.parent, t2.`case`, t2.version, t2.type, t2.`desc`, t2.expect') + ->from(TABLE_CASE)->alias('t1') + ->leftJoin(TABLE_CASESTEP)->alias('t2')->on('t1.version = t2.version') + ->where('t1.id')->in($caseIdList) + ->orderBy('t2.id') + ->fetchGroup('case', 'id'); } /** @@ -393,7 +398,7 @@ class testcaseTao extends testcaseModel protected function updateCase2Project(object $oldCase, object $case): bool { $productChanged = $oldCase->product != $case->product; - $storyChanged = $oldCase->story != $case->story; + $storyChanged = $oldCase->story != zget($case, 'story', $oldCase->story); if(!$productChanged && !$storyChanged) return true; @@ -620,7 +625,7 @@ class testcaseTao extends testcaseModel $this->dao->insert(TABLE_SCENE)->data($scene)->autoCheck()->exec(); $sceneID = $this->dao->lastInsertID(); - $this->dao->update(TABLE_SCENE)->set('`order`')->eq($sceneID)->where('id')->eq($sceneID)->exec(); + $this->dao->update(TABLE_SCENE)->set('sort')->eq($sceneID)->where('id')->eq($sceneID)->exec(); } else { diff --git a/module/testcase/ui/browse.html.php b/module/testcase/ui/browse.html.php index 4f1ba56a77..c0c655ad55 100644 --- a/module/testcase/ui/browse.html.php +++ b/module/testcase/ui/browse.html.php @@ -91,7 +91,7 @@ $footToolbar = $canBatchAction ? array('items' => array $canImportToLib ? array('text' => $lang->testcase->importToLib, 'data-toggle' => 'modal', 'data-target' => '#importToLib', 'data-size' => 'sm') : null, ), 'btnProps' => array('size' => 'sm', 'btnType' => 'secondary')) : null; -$footToolbar['items'] = array_values(array_filter($footToolbar['items'])); +$footToolbar['items'] = $canBatchAction ? array_values(array_filter($footToolbar['items'])) : array(); $cols = $isOnlyScene ? $this->config->scene->dtable->fieldList : $this->loadModel('datatable')->getSetting('testcase'); if(!empty($cols['actions']['list'])) diff --git a/module/testcase/zen.php b/module/testcase/zen.php index 1171013c8c..109bd785b0 100755 --- a/module/testcase/zen.php +++ b/module/testcase/zen.php @@ -1417,10 +1417,11 @@ class testcaseZen extends testcase * 构建导入用例的数据。 * Build cases for showing imported. * + * @param int $productID * @access protected * @return array */ - protected function buildCasesForShowImport(): array + protected function buildCasesForShowImport(int $productID): array { /* 初始化变量。 */ /* Initialize variables. */ @@ -1444,6 +1445,7 @@ class testcaseZen extends testcase $stepChanged = $this->buildUpdateCaseForShowImport($case, $oldCase, zget($oldSteps, $case->rawID, array()), $forceNotReview); $case->id = $case->rawID; + $case->product = $productID; $case->lastEditedBy = $account; $case->lastEditedDate = $now; if($case->story != $oldCase->story) $case->storyVersion = zget($storyVersionPairs, $case->story, 1); @@ -1455,6 +1457,7 @@ class testcaseZen extends testcase /* Build inserted case. */ else { + $case->product = $productID; $case->version = 1; $case->openedBy = $this->app->user->account; $case->openedDate = $now; @@ -1983,6 +1986,7 @@ class testcaseZen extends testcase protected function importCases(array $cases): void { $this->loadModel('action'); + $daoErrors = array(); foreach($cases as $case) { if(isset($case->id)) @@ -2001,9 +2005,10 @@ class testcaseZen extends testcase else { $caseID = $this->testcase->create($case); - $this->testcase->syncCase2Project($case, $caseID); + dao::isError() ? $daoErrors = array_merge($daoErrors, dao::getError()) : $this->testcase->syncCase2Project($case, $caseID); } } + if(!empty($daoErrors)) dao::$errors = $daoErrors; } /** @@ -2773,28 +2778,39 @@ class testcaseZen extends testcase if(isset($relatedSteps[$case->id])) { - $i = $childID = 0; + $preGrade = 1; + $parentSteps = array(); + $key = array(0, 0, 0); foreach($relatedSteps[$case->id] as $step) { - $stepID = 0; - if($step->type == 'group' || $step->type == 'step') + $grade = 1; + $parentSteps[$step->id] = $step->parent; + if(isset($parentSteps[$step->parent])) $grade = isset($parentSteps[$parentSteps[$step->parent]]) ? 3 : 2; + + if($grade > $preGrade) { - $i ++; - $childID = 0; - $stepID = $i; + $key[$grade - 1] = 1; } else { - $stepID = $i . '.' . $childID; + if($grade < $preGrade) + { + if($grade < 2) $key[1] = 0; + if($grade < 3) $key[2] = 0; + } + $key[$grade - 1] ++; } - if($step->version != $case->version) continue; + $stepID = implode('.', $key); + $stepID = str_replace('.0', '', $stepID); + $stepID = str_replace('.0', '', $stepID); $sign = (in_array($this->post->fileType, array('html', 'xml'))) ? '
' : "\n"; $case->stepDesc .= $stepID . ". " . htmlspecialchars_decode($step->desc) . $sign; $case->stepExpect .= $stepID . ". " . htmlspecialchars_decode($step->expect) . $sign; $case->real .= $stepID . ". " . (isset($result[$step->id]) ? $result[$step->id]['real'] : '') . $sign; - $childID ++; + + $preGrade = $grade; } } $case->stepDesc = trim($case->stepDesc); @@ -3113,7 +3129,11 @@ class testcaseZen extends testcase /* Get the content list from the zip file. */ $files = $zip->listContent(); $removePath = $files[0]['filename']; - if($zip->extract(PCLZIP_OPT_PATH, $filePath, PCLZIP_OPT_REMOVE_PATH, $removePath) === 0) return array('result' => 'fail', 'message' => $this->lang->testcase->errorXmindUpload); + + /* 限制解压的文件内容以阻止 ZIP 解压缩的目录穿越漏洞。*/ + /* Limit the file content to prevent the directory traversal vulnerability of ZIP decompression. */ + $extractFiles = array('content.xml', 'content.json'); + if($zip->extract(PCLZIP_OPT_PATH, $filePath, PCLZIP_OPT_BY_NAME, $extractFiles, PCLZIP_OPT_REMOVE_PATH, $removePath) == 0) return array('result' => 'fail', 'message' => $this->lang->testcase->errorXmindUpload); $this->classFile->removeFile($tmpFile); diff --git a/module/testreport/test/testreport.class.php b/module/testreport/test/testreport.class.php index 68dd4c48ad..20cdb62847 100644 --- a/module/testreport/test/testreport.class.php +++ b/module/testreport/test/testreport.class.php @@ -186,6 +186,7 @@ class testreportTest if(dao::isError()) return dao::getError(); + krsort($objects); $return = ''; foreach($objects as $object) $return .= "{$object->name}:{$object->value},"; return trim($return, ','); @@ -209,6 +210,7 @@ class testreportTest if(dao::isError()) return dao::getError(); $return = ''; + ksort($objects); foreach($objects as $object) $return .= "{$object->name}:{$object->value},"; return trim($return, ','); } diff --git a/module/testsuite/css/linkcase.ui.css b/module/testsuite/css/linkcase.ui.css deleted file mode 100644 index 95245bf46e..0000000000 --- a/module/testsuite/css/linkcase.ui.css +++ /dev/null @@ -1 +0,0 @@ -#featureBar .nav .btn > .icon.icon-back{color: var(--color-white);} diff --git a/module/testtask/js/common.ui.js b/module/testtask/js/common.ui.js index eb12c70add..4b72fb6b0f 100644 --- a/module/testtask/js/common.ui.js +++ b/module/testtask/js/common.ui.js @@ -46,9 +46,9 @@ window.loadExecutionBuilds = function() let oldBuild = $('[name="build"]').val(); $buildPicker.render({items: data}); executionID == 0 ? $buildPicker.$.setValue('') : $buildPicker.$.setValue(oldBuild); - if(data.length == 0) + if(data.length == 0 && executionID != 0) { - $('[name="build"]').closest('.input-group').find('.input-group-addon').removeClass('hidden'); + $('[name="build"]').closest('.input-group').find('.input-group-addon').removeClass('hidden').find('a').attr('href', $.createLink('build', 'create', "executionID=" + executionID + "&productID=" + productID + "&projectID=" + projectID)); } else { diff --git a/module/testtask/model.php b/module/testtask/model.php index bfe29270bc..71f9b5afa6 100755 --- a/module/testtask/model.php +++ b/module/testtask/model.php @@ -1089,7 +1089,7 @@ class testtaskModel extends model $caseQuery = str_replace(array('t2.`assignedTo`', 't2.`lastRunner`', 't2.`lastRunDate`', 't2.`lastRunResult`'), array('t1.`assignedTo`', 't1.`lastRunner`', 't1.`lastRunDate`', 't1.`lastRunResult`'), $caseQuery); $orderBy = $this->addPrefixToOrderBy($sort); - return $this->dao->select('t2.*, t1.*, t3.title AS storyTitle, t2.status AS caseStatus')->from(TABLE_TESTRUN)->alias('t1') + return $this->dao->select('t2.*, t1.*, t3.title AS storyTitle, t2.status AS caseStatus, t2.version AS caseVersion')->from(TABLE_TESTRUN)->alias('t1') ->leftJoin(TABLE_CASE)->alias('t2')->on('t1.case = t2.id') ->leftJoin(TABLE_STORY)->alias('t3')->on('t2.story = t3.id') ->where($caseQuery) diff --git a/module/todo/js/batchcreate.ui.js b/module/todo/js/batchcreate.ui.js index 1beea3dc75..b5b9da176e 100644 --- a/module/todo/js/batchcreate.ui.js +++ b/module/todo/js/batchcreate.ui.js @@ -71,15 +71,22 @@ window.initTime = function(e) if(isBegin) { let endValue = ''; - items.forEach(function(item, timeIndex) + if(!value) { - if(item.value == value) + endValue = items[0].value; + } + else + { + items.forEach(function(item, timeIndex) { - endIndex = timeIndex + 3; - endValue = items.length <= endIndex ? value : items[endIndex].value; - return; - } - }); + if(item.value == value) + { + endIndex = timeIndex + 3; + endValue = items.length <= endIndex ? '' : items[endIndex].value; + return; + } + }); + } $end.zui('picker').$.setValue(endValue); if(typeof endIndex != 'undefined') value = endIndex; } diff --git a/module/tree/model.php b/module/tree/model.php index 18966b62e8..244e379a0f 100644 --- a/module/tree/model.php +++ b/module/tree/model.php @@ -1401,6 +1401,19 @@ class treeModel extends model return html::a(helper::createLink('traincourse', 'practicebrowse', "moduleID={$module->id}"), $module->name, '_self', "id='module{$module->id}' title='{$module->name}'"); } + /** + * Create link of practice. + * + * @param string $type + * @param object $module + * @access public + * @return string + */ + public function createPracticeLink(string $type, object $module): string + { + return html::a(helper::createLink('traincourse', 'practicebrowse', "moduleID={$module->id}"), $module->name, '_self', "id='module{$module->id}' title='{$module->name}'"); + } + /** * 生成培训技能的链接。 * Create link of trainskill. @@ -1610,16 +1623,21 @@ class treeModel extends model * Get parents of a module. * * @param int $moduleID + * @param bool $queryAll * @access public * @return array */ - public function getParents(int $moduleID): array + public function getParents(int $moduleID, bool $queryAll = false): array { if($moduleID == 0) return array(); $path = $this->dao->select('path')->from(TABLE_MODULE)->where('id')->eq((int)$moduleID)->fetch('path'); $path = trim($path, ','); if(!$path) return array(); - return $this->dao->select('*')->from(TABLE_MODULE)->where('id')->in($path)->andWhere('deleted')->eq(0)->orderBy('grade')->fetchAll(); + return $this->dao->select('*')->from(TABLE_MODULE) + ->where('id')->in($path) + ->beginIF(!$queryAll)->andWhere('deleted')->eq(0)->fi() + ->orderBy('grade') + ->fetchAll(); } /** diff --git a/module/tree/test/model/getparents.php b/module/tree/test/model/getparents.php index ab02c41d58..b65f39a882 100755 --- a/module/tree/test/model/getparents.php +++ b/module/tree/test/model/getparents.php @@ -1,23 +1,33 @@ #!/usr/bin/env php getParents(); -timeout=0 cid=1 -- 获取 module 1 的父module @,1 +- 获取 module 1 的未删除父module @,1 -- 获取 module 2 的父module @,2 +- 获取 module 2 的未删除父module @,2 -- 获取 module 3 的父module @,3 +- 获取 module 3 的未删除父module @,3 -- 获取 module 6 的父module @,1,6 +- 获取 module 6 的未删除父module @,1,6 -- 获取 module 7 的父module @,2,7 +- 获取 module 7 的未删除父module @,2,7 -- 获取 module 8 的父module @,3,8 +- 获取 module 8 的未删除父module @,3,8 + +- 获取 module 1 的全部父module @,1 + +- 获取 module 2 的全部父module @,2 + +- 获取 module 3 的全部父module @,3 + +- 获取 module 6 的全部父module @,1,6 + +- 获取 module 7 的全部父module @,2,7 + +- 获取 module 8 的全部父module @,3,8 */ include dirname(__FILE__, 5) . '/test/lib/init.php'; @@ -28,9 +38,16 @@ zdTable('module')->config('module')->gen(100); $tree = new treeTest(); -r($tree->getParentsTest(1)) && p() && e(',1'); // 获取 module 1 的父module -r($tree->getParentsTest(2)) && p() && e(',2'); // 获取 module 2 的父module -r($tree->getParentsTest(3)) && p() && e(',3'); // 获取 module 3 的父module -r($tree->getParentsTest(6)) && p() && e(',1,6'); // 获取 module 6 的父module -r($tree->getParentsTest(7)) && p() && e(',2,7'); // 获取 module 7 的父module -r($tree->getParentsTest(8)) && p() && e(',3,8'); // 获取 module 8 的父module \ No newline at end of file +r($tree->getParentsTest(1)) && p() && e(',1'); // 获取 module 1 的未删除父module +r($tree->getParentsTest(2)) && p() && e(',2'); // 获取 module 2 的未删除父module +r($tree->getParentsTest(3)) && p() && e(',3'); // 获取 module 3 的未删除父module +r($tree->getParentsTest(6)) && p() && e(',1,6'); // 获取 module 6 的未删除父module +r($tree->getParentsTest(7)) && p() && e(',2,7'); // 获取 module 7 的未删除父module +r($tree->getParentsTest(8)) && p() && e(',3,8'); // 获取 module 8 的未删除父module + +r($tree->getParentsTest(1, true)) && p() && e(',1'); // 获取 module 1 的全部父module +r($tree->getParentsTest(2, true)) && p() && e(',2'); // 获取 module 2 的全部父module +r($tree->getParentsTest(3, true)) && p() && e(',3'); // 获取 module 3 的全部父module +r($tree->getParentsTest(6, true)) && p() && e(',1,6'); // 获取 module 6 的全部父module +r($tree->getParentsTest(7, true)) && p() && e(',2,7'); // 获取 module 7 的全部父module +r($tree->getParentsTest(8, true)) && p() && e(',3,8'); // 获取 module 8 的全部父module diff --git a/module/tree/test/tree.class.php b/module/tree/test/tree.class.php index 419d3d8d3a..18a7aef48f 100644 --- a/module/tree/test/tree.class.php +++ b/module/tree/test/tree.class.php @@ -551,12 +551,13 @@ class treeTest * Test get parents of a module. * * @param int $moduleID + * @param bool $queryAll * @access public * @return string */ - public function getParentsTest($moduleID) + public function getParentsTest($moduleID, $queryAll = false) { - $objects = $this->objectModel->getParents($moduleID); + $objects = $this->objectModel->getParents($moduleID, $queryAll); if(dao::isError()) return dao::getError(); diff --git a/module/upgrade/config/upgradeflow.php b/module/upgrade/config/upgradeflow.php index 2e90d1c6b3..a65ffbbb83 100644 --- a/module/upgrade/config/upgradeflow.php +++ b/module/upgrade/config/upgradeflow.php @@ -93,7 +93,8 @@ $config->upgrade->execFlow['18_7'] = array('functions' => 'processHistory $config->upgrade->execFlow['18_8'] = array('functions' => 'upgradeTesttaskMembers,deleteGeneralReportBlock,stopOldCron'); $config->upgrade->execFlow['18_9'] = array('functions' => 'addDefaultTraincoursePriv,renameBIModule,migrateXuanClientSettings'); $config->upgrade->execFlow['18_10_1'] = array('functions' => 'migrateAIModelConfig'); -$config->upgrade->execFlow['20_0_alpha1'] = array('functions' => 'revertStoryCustomFields,hideOA,updateMetricDateType,update1810,update18101,migrateAIModelConfig'); +$config->upgrade->execFlow['20_0_alpha1'] = array('functions' => 'revertStoryCustomFields'); +$config->upgrade->execFlow['20_0_beta1'] = array('functions' => 'hideOA,updateMetricDateType,update18101,migrateAIModelConfig'); if(!empty($config->isINT)) { diff --git a/module/upgrade/js/mergeprogram.ui.js b/module/upgrade/js/mergeprogram.ui.js index 1eb2d1bc23..3527ff6808 100644 --- a/module/upgrade/js/mergeprogram.ui.js +++ b/module/upgrade/js/mergeprogram.ui.js @@ -85,8 +85,8 @@ window.changeProjectType = function() const projectType = $('input[name=projectType]:checked').val(); $('.programForm, .formTitle').toggleClass('hidden', projectType == 'project' && mode == 'light'); - $('.createProjectTip').toggleClass('hidden', projectType == 'project'); - $('.createExecutionTip').toggleClass('hidden', projectType == 'execution'); + $('.createProjectTip').toggleClass('hidden', projectType != 'project'); + $('.createExecutionTip').toggleClass('hidden', projectType != 'execution'); $('[name=projectAcl]').closest('.check-list').toggleClass('hidden', projectType == 'project'); $('[name=programAcl]').closest('.check-list').toggleClass('hidden', projectType == 'execution'); $('.projectName').toggleClass('hidden', projectType == 'project'); diff --git a/module/upgrade/model.php b/module/upgrade/model.php index 9e99d63705..73fbe74f0f 100644 --- a/module/upgrade/model.php +++ b/module/upgrade/model.php @@ -8554,14 +8554,16 @@ class upgradeModel extends model } /** - * 检查是否添加了18.10版本的需求池字段,如果没有就添加。 - * Check if the demand pool field of version 18.10 is added, if not, add it. + * 检查是否添加了18.10.1版本的 AI 表,如果没有就添加。 + * Check if the AI table of version 18.10.1 is added, if not, add it. * * @access public * @return void */ public function update1810(): void { + if(!defined('TABLE_DEMANDPOOL')) return; + $fields = $this->dao->query('DESC ' . TABLE_DEMANDPOOL)->fetchAll(); foreach($fields as $field) { @@ -8576,17 +8578,15 @@ class upgradeModel extends model /** * 检查是否添加了18.10.1版本的需求反馈字段,如果没有就添加。 * Check if the demand feedback field of version 18.10.1 is added, if not, add it. +>>>>>>> * Fix bug for upgrade. * * @access public * @return void */ public function update18101(): void { - $fields = $this->dao->query('DESC ' . TABLE_DEMAND)->fetchAll(); - foreach($fields as $field) - { - if($field->Field == 'feedback') return; - } + $count = $this->dao->select('COUNT(*) AS count')->from('information_schema.TABLES')->where('TABLE_SCHEMA')->eq($this->config->db->name)->andWhere('TABLE_NAME')->eq(str_replace('`', '', TABLE_AI_MODEL))->fetch('count'); + if($count) return; /* Execute open edition. */ $this->saveLogs('Execute 18_10_1'); diff --git a/module/upgrade/test/model/computeproductacl.php b/module/upgrade/test/model/computeproductacl.php index 7bdb8281c2..380186ea7b 100644 --- a/module/upgrade/test/model/computeproductacl.php +++ b/module/upgrade/test/model/computeproductacl.php @@ -66,7 +66,7 @@ zdTable('user')->gen(5); $product = zdTable('product'); $product->program->range('0{2},1'); -$product->acl->range('custom,public,private'); +$product->acl->range('custom,open,private'); $product->gen(9); su('admin'); @@ -77,31 +77,31 @@ $productIdList = array(array(1, 2, 3), array(4, 5, 6), array(7, 8, 9), array(10, $programID = array(0, 1, 2); $lineID = array(0, 1, null); -r($upgrade->computeProductAclTest($productIdList[0], $programID[0], $lineID[0])) && p() && e('1:0,private,0; 2:0,,0; 3:1,private,2;'); // 测试更新产品 1 2 3 的项目集ID改为 0 lineID改为 0 -r($upgrade->computeProductAclTest($productIdList[0], $programID[0], $lineID[1])) && p() && e('1:0,private,1; 2:0,,1; 3:1,private,2;'); // 测试更新产品 1 2 3 的项目集ID改为 0 lineID改为 1 -r($upgrade->computeProductAclTest($productIdList[0], $programID[0], $lineID[2])) && p() && e('1:0,private,1; 2:0,,1; 3:1,private,2;'); // 测试更新产品 1 2 3 的项目集ID改为 0 lineID改为 null -r($upgrade->computeProductAclTest($productIdList[0], $programID[1], $lineID[0])) && p() && e('1:1,private,0; 2:1,,0; 3:1,private,2;'); // 测试更新产品 1 2 3 的项目集ID改为 1 lineID改为 0 -r($upgrade->computeProductAclTest($productIdList[0], $programID[1], $lineID[1])) && p() && e('1:1,private,0; 2:1,,0; 3:1,private,2;'); // 测试更新产品 1 2 3 的项目集ID改为 1 lineID改为 1 -r($upgrade->computeProductAclTest($productIdList[0], $programID[1], $lineID[2])) && p() && e('1:1,private,0; 2:1,,0; 3:1,private,2;'); // 测试更新产品 1 2 3 的项目集ID改为 1 lineID改为 null -r($upgrade->computeProductAclTest($productIdList[0], $programID[2], $lineID[0])) && p() && e('1:1,private,0; 2:1,,0; 3:1,private,2;'); // 测试更新产品 1 2 3 的项目集ID改为 2 lineID改为 0 -r($upgrade->computeProductAclTest($productIdList[0], $programID[2], $lineID[1])) && p() && e('1:1,private,0; 2:1,,0; 3:1,private,2;'); // 测试更新产品 1 2 3 的项目集ID改为 2 lineID改为 1 -r($upgrade->computeProductAclTest($productIdList[0], $programID[2], $lineID[2])) && p() && e('1:1,private,0; 2:1,,0; 3:1,private,2;'); // 测试更新产品 1 2 3 的项目集ID改为 2 lineID改为 null +r($upgrade->computeProductAclTest($productIdList[0], $programID[0], $lineID[0])) && p() && e('1:0,private,0; 2:0,open,0; 3:1,private,2;'); // 测试更新产品 1 2 3 的项目集ID改为 0 lineID改为 0 +r($upgrade->computeProductAclTest($productIdList[0], $programID[0], $lineID[1])) && p() && e('1:0,private,1; 2:0,open,1; 3:1,private,2;'); // 测试更新产品 1 2 3 的项目集ID改为 0 lineID改为 1 +r($upgrade->computeProductAclTest($productIdList[0], $programID[0], $lineID[2])) && p() && e('1:0,private,1; 2:0,open,1; 3:1,private,2;'); // 测试更新产品 1 2 3 的项目集ID改为 0 lineID改为 null +r($upgrade->computeProductAclTest($productIdList[0], $programID[1], $lineID[0])) && p() && e('1:1,private,0; 2:1,open,0; 3:1,private,2;'); // 测试更新产品 1 2 3 的项目集ID改为 1 lineID改为 0 +r($upgrade->computeProductAclTest($productIdList[0], $programID[1], $lineID[1])) && p() && e('1:1,private,0; 2:1,open,0; 3:1,private,2;'); // 测试更新产品 1 2 3 的项目集ID改为 1 lineID改为 1 +r($upgrade->computeProductAclTest($productIdList[0], $programID[1], $lineID[2])) && p() && e('1:1,private,0; 2:1,open,0; 3:1,private,2;'); // 测试更新产品 1 2 3 的项目集ID改为 1 lineID改为 null +r($upgrade->computeProductAclTest($productIdList[0], $programID[2], $lineID[0])) && p() && e('1:1,private,0; 2:1,open,0; 3:1,private,2;'); // 测试更新产品 1 2 3 的项目集ID改为 2 lineID改为 0 +r($upgrade->computeProductAclTest($productIdList[0], $programID[2], $lineID[1])) && p() && e('1:1,private,0; 2:1,open,0; 3:1,private,2;'); // 测试更新产品 1 2 3 的项目集ID改为 2 lineID改为 1 +r($upgrade->computeProductAclTest($productIdList[0], $programID[2], $lineID[2])) && p() && e('1:1,private,0; 2:1,open,0; 3:1,private,2;'); // 测试更新产品 1 2 3 的项目集ID改为 2 lineID改为 null -r($upgrade->computeProductAclTest($productIdList[1], $programID[0], $lineID[0])) && p() && e('4:0,private,0; 5:0,,0; 6:1,private,5;'); // 测试更新产品 4 5 6 的项目集ID改为 0 lineID改为 0 -r($upgrade->computeProductAclTest($productIdList[1], $programID[0], $lineID[1])) && p() && e('4:0,private,1; 5:0,,1; 6:1,private,5;'); // 测试更新产品 4 5 6 的项目集ID改为 0 lineID改为 1 -r($upgrade->computeProductAclTest($productIdList[1], $programID[0], $lineID[2])) && p() && e('4:0,private,1; 5:0,,1; 6:1,private,5;'); // 测试更新产品 4 5 6 的项目集ID改为 0 lineID改为 null -r($upgrade->computeProductAclTest($productIdList[1], $programID[1], $lineID[0])) && p() && e('4:1,private,0; 5:1,,0; 6:1,private,5;'); // 测试更新产品 4 5 6 的项目集ID改为 1 lineID改为 0 -r($upgrade->computeProductAclTest($productIdList[1], $programID[1], $lineID[1])) && p() && e('4:1,private,0; 5:1,,0; 6:1,private,5;'); // 测试更新产品 4 5 6 的项目集ID改为 1 lineID改为 1 -r($upgrade->computeProductAclTest($productIdList[1], $programID[1], $lineID[2])) && p() && e('4:1,private,0; 5:1,,0; 6:1,private,5;'); // 测试更新产品 4 5 6 的项目集ID改为 1 lineID改为 null -r($upgrade->computeProductAclTest($productIdList[1], $programID[2], $lineID[0])) && p() && e('4:1,private,0; 5:1,,0; 6:1,private,5;'); // 测试更新产品 4 5 6 的项目集ID改为 2 lineID改为 0 -r($upgrade->computeProductAclTest($productIdList[1], $programID[2], $lineID[1])) && p() && e('4:1,private,0; 5:1,,0; 6:1,private,5;'); // 测试更新产品 4 5 6 的项目集ID改为 2 lineID改为 1 -r($upgrade->computeProductAclTest($productIdList[1], $programID[2], $lineID[2])) && p() && e('4:1,private,0; 5:1,,0; 6:1,private,5;'); // 测试更新产品 4 5 6 的项目集ID改为 2 lineID改为 null +r($upgrade->computeProductAclTest($productIdList[1], $programID[0], $lineID[0])) && p() && e('4:0,private,0; 5:0,open,0; 6:1,private,5;'); // 测试更新产品 4 5 6 的项目集ID改为 0 lineID改为 0 +r($upgrade->computeProductAclTest($productIdList[1], $programID[0], $lineID[1])) && p() && e('4:0,private,1; 5:0,open,1; 6:1,private,5;'); // 测试更新产品 4 5 6 的项目集ID改为 0 lineID改为 1 +r($upgrade->computeProductAclTest($productIdList[1], $programID[0], $lineID[2])) && p() && e('4:0,private,1; 5:0,open,1; 6:1,private,5;'); // 测试更新产品 4 5 6 的项目集ID改为 0 lineID改为 null +r($upgrade->computeProductAclTest($productIdList[1], $programID[1], $lineID[0])) && p() && e('4:1,private,0; 5:1,open,0; 6:1,private,5;'); // 测试更新产品 4 5 6 的项目集ID改为 1 lineID改为 0 +r($upgrade->computeProductAclTest($productIdList[1], $programID[1], $lineID[1])) && p() && e('4:1,private,0; 5:1,open,0; 6:1,private,5;'); // 测试更新产品 4 5 6 的项目集ID改为 1 lineID改为 1 +r($upgrade->computeProductAclTest($productIdList[1], $programID[1], $lineID[2])) && p() && e('4:1,private,0; 5:1,open,0; 6:1,private,5;'); // 测试更新产品 4 5 6 的项目集ID改为 1 lineID改为 null +r($upgrade->computeProductAclTest($productIdList[1], $programID[2], $lineID[0])) && p() && e('4:1,private,0; 5:1,open,0; 6:1,private,5;'); // 测试更新产品 4 5 6 的项目集ID改为 2 lineID改为 0 +r($upgrade->computeProductAclTest($productIdList[1], $programID[2], $lineID[1])) && p() && e('4:1,private,0; 5:1,open,0; 6:1,private,5;'); // 测试更新产品 4 5 6 的项目集ID改为 2 lineID改为 1 +r($upgrade->computeProductAclTest($productIdList[1], $programID[2], $lineID[2])) && p() && e('4:1,private,0; 5:1,open,0; 6:1,private,5;'); // 测试更新产品 4 5 6 的项目集ID改为 2 lineID改为 null -r($upgrade->computeProductAclTest($productIdList[2], $programID[0], $lineID[2])) && p() && e('7:0,private,6; 8:0,,7; 9:1,private,8;'); // 测试更新产品 7 8 9 的项目集ID改为 0 lineID改为 null -r($upgrade->computeProductAclTest($productIdList[2], $programID[0], $lineID[1])) && p() && e('7:0,private,1; 8:0,,1; 9:1,private,8;'); // 测试更新产品 7 8 9 的项目集ID改为 0 lineID改为 1 -r($upgrade->computeProductAclTest($productIdList[2], $programID[1], $lineID[2])) && p() && e('7:1,private,1; 8:1,,1; 9:1,private,8;'); // 测试更新产品 7 8 9 的项目集ID改为 1 lineID改为 null -r($upgrade->computeProductAclTest($productIdList[2], $programID[1], $lineID[1])) && p() && e('7:1,private,1; 8:1,,1; 9:1,private,8;'); // 测试更新产品 7 8 9 的项目集ID改为 1 lineID改为 1 -r($upgrade->computeProductAclTest($productIdList[2], $programID[2], $lineID[2])) && p() && e('7:1,private,1; 8:1,,1; 9:1,private,8;'); // 测试更新产品 7 8 9 的项目集ID改为 2 lineID改为 null -r($upgrade->computeProductAclTest($productIdList[2], $programID[2], $lineID[1])) && p() && e('7:1,private,1; 8:1,,1; 9:1,private,8;'); // 测试更新产品 7 8 9 的项目集ID改为 2 lineID改为 1 +r($upgrade->computeProductAclTest($productIdList[2], $programID[0], $lineID[2])) && p() && e('7:0,private,6; 8:0,open,7; 9:1,private,8;'); // 测试更新产品 7 8 9 的项目集ID改为 0 lineID改为 null +r($upgrade->computeProductAclTest($productIdList[2], $programID[0], $lineID[1])) && p() && e('7:0,private,1; 8:0,open,1; 9:1,private,8;'); // 测试更新产品 7 8 9 的项目集ID改为 0 lineID改为 1 +r($upgrade->computeProductAclTest($productIdList[2], $programID[1], $lineID[2])) && p() && e('7:1,private,1; 8:1,open,1; 9:1,private,8;'); // 测试更新产品 7 8 9 的项目集ID改为 1 lineID改为 null +r($upgrade->computeProductAclTest($productIdList[2], $programID[1], $lineID[1])) && p() && e('7:1,private,1; 8:1,open,1; 9:1,private,8;'); // 测试更新产品 7 8 9 的项目集ID改为 1 lineID改为 1 +r($upgrade->computeProductAclTest($productIdList[2], $programID[2], $lineID[2])) && p() && e('7:1,private,1; 8:1,open,1; 9:1,private,8;'); // 测试更新产品 7 8 9 的项目集ID改为 2 lineID改为 null +r($upgrade->computeProductAclTest($productIdList[2], $programID[2], $lineID[1])) && p() && e('7:1,private,1; 8:1,open,1; 9:1,private,8;'); // 测试更新产品 7 8 9 的项目集ID改为 2 lineID改为 1 r($upgrade->computeProductAclTest($productIdList[3], $programID[0], $lineID[0])) && p() && e('0'); // 测试更新不存在的产品 10 11 12 的项目集ID改为 0 lineID改为 0 diff --git a/module/upgrade/test/model/createproject.php b/module/upgrade/test/model/createproject.php index 6521369a68..2247475569 100644 --- a/module/upgrade/test/model/createproject.php +++ b/module/upgrade/test/model/createproject.php @@ -27,8 +27,8 @@ zdTable('user')->gen(5); zdTable('action')->gen(0); $project = zdTable('project'); -$project->begin->range('2023-11-01'); -$project->end->range('2023-12-31'); +$project->begin->range('2023-11-01')->type('timestamp')->format('YYYY-MM-DD'); +$project->end->range('2023-12-31')->type('timestamp')->format('YYYY-MM-DD'); $project->gen(2); su('admin'); @@ -62,9 +62,9 @@ $emptyName->PM = 'user1'; $programID = array(1, 2); -r($upgrade->createProjectTest($programID[0], $project1)) && p() && e('project:3,项目1,2059-12-31,open; program:0000-00-00,0000-00-00; actionCount:2'); // 测试创建 project1 到项目集 1 -r($upgrade->createProjectTest($programID[0], $project2)) && p() && e('project:4,项目2,2024-12-31,open; program:0000-00-00,2024-12-31; actionCount:1'); // 测试创建 project2 到项目集 1 -r($upgrade->createProjectTest($programID[1], $project1)) && p() && e('project:5,项目1,2059-12-31,open; program:0000-00-00,0000-00-00; actionCount:2'); // 测试创建 project1 到项目集 2 -r($upgrade->createProjectTest($programID[1], $project2)) && p() && e('project:6,项目2,2024-12-31,open; program:0000-00-00,2024-12-31; actionCount:1'); // 测试创建 project2 到项目集 2 +r($upgrade->createProjectTest($programID[0], $project1)) && p() && e('project:3,项目1,2059-12-31,open; program:2023-12-01,2024-03-12; actionCount:2'); // 测试创建 project1 到项目集 1 +r($upgrade->createProjectTest($programID[0], $project2)) && p() && e('project:4,项目2,2024-12-31,open; program:2021-12-01,2024-12-31; actionCount:1'); // 测试创建 project2 到项目集 1 +r($upgrade->createProjectTest($programID[1], $project1)) && p() && e('project:5,项目1,2059-12-31,open; program:2023-12-01,2024-03-12; actionCount:2'); // 测试创建 project1 到项目集 2 +r($upgrade->createProjectTest($programID[1], $project2)) && p() && e('project:6,项目2,2024-12-31,open; program:2021-12-01,2024-12-31; actionCount:1'); // 测试创建 project2 到项目集 2 r($upgrade->createProjectTest($programID[0], $emptyName)) && p('name:0') && e('『项目名称』不能为空。'); // 测试创建 项目名称空 的项目到项目集 1 r($upgrade->createProjectTest($programID[1], $project1)) && p('name:0') && e('『项目名称』已经有『项目1』这条记录了。如果您确定该记录已删除,请到后台-系统设置-回收站还原。'); // 测试重复创建 project1 到项目集 2 diff --git a/module/upgrade/ui/createprogram.html.php b/module/upgrade/ui/createprogram.html.php index 4685fe4e7b..3e17c3d144 100644 --- a/module/upgrade/ui/createprogram.html.php +++ b/module/upgrade/ui/createprogram.html.php @@ -25,7 +25,7 @@ $createProgram = function($data) div(setClass('createProjectTip text-gray ' . ($data->projectType == 'project' ? '' : 'hidden')), html($lang->upgrade->createProjectTip)), div(setClass('createExecutionTip text-gray ' . ($data->projectType == 'execution' ? '' : 'hidden')), html($lang->upgrade->createExecutionTip)) ), - formRowGroup(set::class('formTitle'), set::title($data->systemMode == 'light' ? $lang->upgrade->setProject : $lang->upgrade->setProgram), set::items(array())), + formRowGroup(setClass('formTitle'), set::title($data->systemMode == 'light' ? $lang->upgrade->setProject : $lang->upgrade->setProgram), set::items(array())), div ( setClass('programForm mt-4 form-horz'), diff --git a/module/user/ui/bug.html.php b/module/user/ui/bug.html.php index b800c17b50..11c82c9c9f 100644 --- a/module/user/ui/bug.html.php +++ b/module/user/ui/bug.html.php @@ -47,6 +47,8 @@ div set::bordered(true), set::cols($cols), set::data(array_values($bugs)), + set::priList($lang->bug->priList), + set::severityList($lang->bug->severityList), set::orderBy($orderBy), set::sortLink(inlink('bug', "userID={$user->id}&type={$type}&orderBy={name}_{sortType}&recTotal={$pager->recTotal}&recPerPage={$pager->recPerPage}&pageID={$pager->pageID}")), set::footPager(usePager()) diff --git a/module/zanode/js/create.ui.js b/module/zanode/js/create.ui.js index 636f1cd848..4aa73041c2 100644 --- a/module/zanode/js/create.ui.js +++ b/module/zanode/js/create.ui.js @@ -24,10 +24,10 @@ function onChangeType() } } -function onHostChange(event) +function onHostChange(host) { - var hostID = $(event.target).val(); - var link = $.createLink('zanode', 'ajaxGetImages', 'hostID=' + hostID); + const hostID = typeof host == 'object' ? $('[name=parent]').val() : host; + var link = $.createLink('zanode', 'ajaxGetImages', 'hostID=' + hostID); $.get(link, function(data) { if(data) @@ -106,3 +106,8 @@ function onChangeSystem(event) $osPicker.render({items: osItems}); $osPicker.$.clear(); } + +$(function() +{ + if(hostID) onHostChange(hostID); +}); diff --git a/module/zanode/ui/create.html.php b/module/zanode/ui/create.html.php index 6a19ee0f0d..ade04318d8 100644 --- a/module/zanode/ui/create.html.php +++ b/module/zanode/ui/create.html.php @@ -12,6 +12,7 @@ namespace zin; jsVar('linuxList', $config->zanode->linuxList); jsVar('windowsList', $config->zanode->windowsList); +jsVar('hostID', $hostID); formPanel ( @@ -26,10 +27,10 @@ formPanel ( array ( - 'toggle' => 'tooltip', - 'placement' => 'right', - 'title' => $lang->zanode->tips, - 'type' => 'white', + 'toggle' => 'tooltip', + 'placement' => 'right', + 'title' => $lang->zanode->tips, + 'type' => 'white', 'class-name' => 'text-gray border border-light' ) ), @@ -181,5 +182,3 @@ formPanel set::control('editor') ) ); - -render(); diff --git a/test/data/cron.yaml b/test/data/cron.yaml index d6e6862eba..b9dee9d29b 100644 --- a/test/data/cron.yaml +++ b/test/data/cron.yaml @@ -81,10 +81,6 @@ fields: loop: 0 format: "" - field: lastTime - note: "最后执行时间" - from: common.date.v1.yaml - use: dateA - prefix: "" - postfix: "" - loop: 0 - format: "" + range: "(-1M)-(+1w):-1D" + type: timestamp + format: "YYYY-MM-DD hh:mm:ss" diff --git a/test/data/project.yaml b/test/data/project.yaml index dfd3661f04..521a2ea0ec 100644 --- a/test/data/project.yaml +++ b/test/data/project.yaml @@ -94,5 +94,7 @@ fields: range: 1 - field: category range: '' + - field: hasProduct + range: 1 - field: deleted range: 0 diff --git a/test/data/searchindex.yaml b/test/data/searchindex.yaml index c53d3f47a6..0ab49b69e7 100644 --- a/test/data/searchindex.yaml +++ b/test/data/searchindex.yaml @@ -40,17 +40,11 @@ fields: format: "" - field: addedDate note: "添加日期" - from: common.date.v1.yaml - use: dateA - prefix: "" - postfix: "" - loop: 0 - format: "" + range: "(-1M)-(+1w):-1D" + type: timestamp + format: "YYYY-MM-DD hh:mm:ss" - field: editedDate note: "编辑日期" - from: common.date.v1.yaml - use: dateA - prefix: "" - postfix: "" - loop: 0 - format: "" + range: "(-1M)-(+1w):-1D" + type: timestamp + format: "YYYY-MM-DD hh:mm:ss" diff --git a/www/js/zui/min.js b/www/js/zui/min.js index 95ad52d9b9..a56f698491 100644 --- a/www/js/zui/min.js +++ b/www/js/zui/min.js @@ -1,7 +1,7 @@ /*! * ZUI: ZUI for Zentao - v1.10.0 - 2024-03-08 * http://openzui.com - * GitHub: https://github.com/easysoft/zui.git + * GitHub: https://github.com/easysoft/zui.git * Copyright (c) 2024 cnezsoft.com; Licensed MIT */ function showEditCheckbox(t){if($(".icon-project,.icon-waterfall,.icon-scrum,.icon-kanban,.icon-agileplus,.icon-waterfallplus,.icon-ipd").each(function(){if($this=$(this),$tr=$(this).closest("tr"),projectID=$tr.attr("data-id"),t){var e=$tr.find("td:first").find("span.table-nest-icon").css("margin-left");$tr.find("td:first").prepend("
"),$tr.find("td:first").find(".checkbox-primary").css("margin-left",e).css("width","14"),$tr.find("td:first").find("span.table-nest-icon").css("margin-left","0")}else{var e=$tr.find("td:first").find(".checkbox-primary").css("margin-left");$tr.find("td:first").find("span.table-nest-icon").css("margin-left",e),$tr.find("td:first").find('[name^="projectIdList"]').parent().remove()}}),t&&hasProject){var e="
";$("#programForm").attr("action",createLink("project","batchEdit","from=program")),$(".table-footer").prepend(e).show(),$("body").scroll()}else $("#programForm").removeClass("has-row-checked"),$("#projectsSummary").addClass("hidden"),$("#programSummary").removeClass("hidden"),$("#programForm").find(".editCheckbox").remove(),0==$("#programForm .pager").length&&$(".table-footer").hide(),$("#programForm").removeAttr("action")}!function(t,e,i){"use strict";if("undefined"==typeof t)throw new Error("ZUI requires jQuery");Number.isNaN||"function"!=typeof isNaN||(Number.isNaN=isNaN),Number.parseInt||"function"!=typeof parseInt||(Number.parseInt=parseInt),Number.parseFloat||"function"!=typeof parseFloat||(Number.parseFloat=parseFloat),t.zui||(t.zui=function(e){t.isPlainObject(e)&&t.extend(t.zui,e)});var n={all:-1,left:0,middle:1,right:2},o=0;t.zui({uuid:function(t){var e=1e5*(Date.now()-1580890015292)+10*Math.floor(1e4*Math.random())+o++%10;return t?e:e.toString(36)},callEvent:function(t,e,n){if("function"==typeof t){n!==i&&(t=t.bind(n));var o=t(e);return e&&(e.result=o),!(o!==i&&!o)}return 1},strCode:function(t){var e=0;if("string"!=typeof t&&(t=String(t)),t&&t.length)for(var i=0;i=e.innerWidth?0:t.zui.getScrollbarSize()},fixBodyScrollbar:function(){if(t.zui.checkBodyScrollbar()){var e=t("body"),i=parseInt(e.css("padding-right")||0,10);return t.zui._scrollbarWidth&&e.css({paddingRight:i+t.zui._scrollbarWidth,overflowY:"hidden"}),!0}},resetBodyScrollbar:function(){t("body").css({paddingRight:"",overflowY:""})}}),t.fn.callEvent=function(e,n,o){var a=t(this),s=e.indexOf(".zui."),r=s<0?e:e.substring(0,s),l=t.Event(r,n);if(o===i&&s>0&&(o=a.data(e.substring(s+1))),o&&o.options){var c=o.options[r];"function"==typeof c&&(l.result=t.zui.callEvent(c,l,o))}return a.trigger(l),l},t.fn.callComEvent=function(t,e,n){n===i||Array.isArray(n)||(n=[n]);var o,a=this;a.trigger(e,n);var s=t.options[e];return s&&(o=s.apply(t,n)),o}}(jQuery,window,void 0),function(){"use strict";function t(t,e){return i&&!e?requestAnimationFrame(t):setTimeout(t,e||0)}function e(t){return i?cancelAnimationFrame(t):void clearTimeout(t)}var i="function"==typeof window.requestAnimationFrame;$.zui({asap:t,clearAsap:e})}(),function(t){"use strict";t.fn.fixOlPd=function(e){return e=e||10,this.each(function(){var i=t(this);i.css("paddingLeft",Math.ceil(Math.log10(i.children().length))*e+10)})},t(function(){t(".ol-pd-fix,.article ol").fixOlPd()})}(jQuery),+function(t){"use strict";var e='[data-dismiss="alert"]',i="zui.alert",n=function(i){t(i).on("click",e,this.close)};n.prototype.close=function(e){function n(){s.trigger("closed."+i).remove()}var o=t(this),a=o.attr("data-target");a||(a=o.attr("href"),a=a&&a.replace(/.*(?=#[^\s]*$)/,""));var s=t(a);e&&e.preventDefault(),s.length||(s=o.hasClass("alert")?o:o.parent()),s.trigger(e=t.Event("close."+i)),e.isDefaultPrevented()||(s.removeClass("in"),t.support.transition&&s.hasClass("fade")?s.one(t.support.transition.end,n).emulateTransitionEnd(150):n())};var o=t.fn.alert;t.fn.alert=function(e){return this.each(function(){var o=t(this),a=o.data(i);a||o.data(i,a=new n(this)),"string"==typeof e&&a[e].call(o)})},t.fn.alert.Constructor=n,t.fn.alert.noConflict=function(){return t.fn.alert=o,this},t(document).on("click."+i+".data-api",e,n.prototype.close)}(window.jQuery),function(t,e){"use strict";var i="zui.pager",n={page:1,recTotal:0,recPerPage:10},o={zh_cn:{pageOfText:"第 {0} 页",prev:"上一页",next:"下一页",first:"第一页",last:"最后一页","goto":"跳转",pageOf:"第 {page} 页",totalPage:"共 {totalPage} 页",totalCount:"共 {recTotal} 项",pageSize:"每页 {recPerPage} 项",itemsRange:"第 {start} ~ {end} 项",pageOfTotal:"第 {page}/{totalPage} 页"},zh_tw:{pageOfText:"第 {0} 頁",prev:"上一頁",next:"下一頁",first:"第一頁",last:"最後一頁","goto":"跳轉",pageOf:"第 {page} 頁",totalPage:"共 {totalPage} 頁",totalCount:"共 {recTotal} 項",pageSize:"每頁 {recPerPage} 項",itemsRange:"第 {start} ~ {end} 項",pageOfTotal:"第 {page}/{totalPage} 頁"},en:{pageOfText:"Page {0}",prev:"Prev",next:"Next",first:"First",last:"Last","goto":"Goto",pageOf:"Page {page}",totalPage:"{totalPage} pages",totalCount:"Total: {recTotal} items",pageSize:"{recPerPage} per page",itemsRange:"From {start} to {end}",pageOfTotal:"Page {page} of {totalPage}"}},a=function(e,n){var s=this;s.name=i,s.$=t(e),n=s.options=t.extend({},a.DEFAULTS,this.$.data(),n),s.langName=n.lang||t.zui.clientLang(),s.lang=t.zui.getLangData(i,s.langName,o),s.state={},s.set(n.page,n.recTotal,n.recPerPage,!0),s.$.on("click",".pager-goto-btn",function(){var e=t(this).closest(".pager-goto"),i=parseInt(e.find(".pager-goto-input").val());NaN!==i&&s.set(i)}).on("click",".pager-item",function(){var e=t(this).data("page");"number"==typeof e&&e>0&&s.set(e)}).on("click",".pager-size-menu [data-size]",function(){var e=t(this).data("size");"number"==typeof e&&e>0&&s.set(-1,-1,e)})};a.prototype.set=function(e,i,o,a){var s=this;"object"==typeof e&&null!==e&&(o=e.recPerPage,i=e.recTotal,e=e.page);var r=s.state;r||(r=t.extend({},n));var l=t.extend({},r);return"number"==typeof o&&o>0&&(r.recPerPage=o),"number"==typeof i&&i>=0&&(r.recTotal=i),"number"==typeof e&&e>=0&&(r.page=e),r.totalPage=r.recTotal&&r.recPerPage?Math.ceil(r.recTotal/r.recPerPage):1,r.page=Math.max(0,Math.min(r.page,r.totalPage)),r.pageRecCount=r.recTotal,r.page&&r.recTotal&&(r.page1&&(r.pageRecCount=r.recTotal-r.recPerPage*(r.page-1))),r.skip=r.page>1?(r.page-1)*r.recPerPage:0,r.start=r.skip+1,r.end=r.skip+r.pageRecCount,r.prev=r.page>1?r.page-1:0,r.next=r.page').attr("href",i?a.createLink(i,a.state):"###").html(n);return o||(s=t("
  • ").append(s).toggleClass("active",i===a.state.page).toggleClass("disabled",!i||i===a.state.page)),s},a.prototype.createNavItems=function(t){var i=this,n=i.$,o=i.state,a=o.totalPage,s=o.page,r=function(t,o){if(t===!1)return void n.append(i.createLinkItem(0,o||i.options.navEllipsisItem));o===e&&(o=t);for(var a=t;a<=o;++a)n.append(i.createLinkItem(a))};t===e&&(t=i.options.maxNavCount||10),r(1),a>1&&(a<=t?r(2,a):sa-t+2?(r(!1),r(a-t+2,a)):(r(!1),r(s-Math.ceil((t-4)/2),s+Math.floor((t-4)/2)),r(!1),r(a)))},a.prototype.createGoto=function(){var e=this,i=this.state,n=t('
    ");return n},a.prototype.createSizeMenu=function(){var e=this,i=this.state,n=t(''),o=e.options.pageSizeOptions;"string"==typeof o&&(o=o.split(","));for(var a=0;a'+s+"
  • ").toggleClass("active",s===i.recPerPage);n.append(r)}return t('
    ').addClass(e.options.menuDirection).append(n)},a.prototype.createElement=function(e,i,n){var o=this,a=o.createLinkItem.bind(o),s=o.lang;switch(e){case"prev":return a(n.prev,s.prev);case"prev_icon":return a(n.prev,'');case"next":return a(n.next,s.next);case"next_icon":return a(n.next,'');case"first":return a(1,s.first);case"first_icon":return a(1,'');case"last":return a(n.totalPage,s.last);case"last_icon":return a(n.totalPage,'');case"space":case"|":return t('
  • ');case"nav":case"pages":return void o.createNavItems();case"total_text":return t(('
    '+s.totalCount+"
    ").format(n));case"page_text":return t(('
    '+s.pageOf+"
    ").format(n));case"total_page_text":return t(('
    '+s.totalPage+"
    ").format(n));case"page_of_total_text":return t(('
    '+s.pageOfTotal+"
    ").format(n));case"page_size_text":return t(('
    '+s.pageSize+"
    ").format(n));case"items_range_text":return t(('
    '+s.itemsRange+"
    ").format(n));case"goto":return o.createGoto();case"size_menu":return o.createSizeMenu();default:return t("
  • ").html(e.format(n))}},a.prototype.createLink=function(i,n){i===e&&(i=this.state.page),n===e&&(n=this.state);var o=this.options.linkCreator;return"string"==typeof o?o.format(t.extend({},n,{page:i})):"function"==typeof o?o(i,n):"#page="+i},a.prototype.render=function(e){var i=this,n=i.state,o=i.options.elementCreator||i.createElement,a=t.isPlainObject(o);e=e||i.elements||i.options.elements,"string"==typeof e&&(e=e.split(",")),i.elements=e,i.$.empty();for(var s=0;s").append(c)),i.$.append(c))}var h=null;return i.$.children("li").each(function(){var e=t(this),i=!!e.children(".pager-item").length;h?h.toggleClass("pager-item-right",!i):i&&e.addClass("pager-item-left"),h=i?e:null}),h&&h.addClass("pager-item-right"),i.$.callComEvent(i,"onRender",[n]),i},a.DEFAULTS=t.extend({elements:["first_icon","prev_icon","pages","next_icon","last_icon","page_of_total_text","items_range_text","total_text"],prevIcon:"icon-double-angle-left",nextIcon:"icon-double-angle-right",firstIcon:"icon-step-backward",lastIcon:"icon-step-forward",navEllipsisItem:'',maxNavCount:10,menuDirection:"dropdown",pageSizeOptions:[10,20,30,50,100]},n),t.fn.pager=function(e){return this.each(function(){var n=t(this),o=n.data(i),s="object"==typeof e&&e;o||n.data(i,o=new a(this,s)),"string"==typeof e&&o[e]()})},a.NAME=i,a.LANG=o,t.fn.pager.Constructor=a,t(function(){t('[data-ride="pager"]').pager()})}(jQuery,void 0),+function(t){"use strict";var e="zui.tab",i=function(e){this.element=t(e)};i.prototype.show=function(){var i=this.element,n=i.closest("ul:not(.dropdown-menu)"),o=i.attr("data-target")||i.attr("data-tab");if(o||(o=i.attr("href"),o=o&&o.replace(/.*(?=#[^\s]*$)/,"")),!i.parent("li").hasClass("active")){var a=n.find(".active:last a")[0],s=t.Event("show."+e,{relatedTarget:a});if(i.trigger(s),!s.isDefaultPrevented()){var r=t(o);this.activate(i.parent("li"),n),this.activate(r,r.parent(),function(){i.trigger({type:"shown."+e,relatedTarget:a})})}}},i.prototype.activate=function(e,i,n){function o(){a.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),e.addClass("active"),s?(e[0].offsetWidth,e.addClass("in")):e.removeClass("fade"),e.parent(".dropdown-menu")&&e.closest("li.dropdown").addClass("active"),n&&n()}var a=i.find("> .active"),s=n&&t.support.transition&&a.hasClass("fade");s?a.one(t.support.transition.end,o).emulateTransitionEnd(150):o(),a.removeClass("in")};var n=t.fn.tab;t.fn.tab=function(n){return this.each(function(){var o=t(this),a=o.data(e);a||o.data(e,a=new i(this)),"string"==typeof n&&a[n]()})},t.fn.tab.Constructor=i,t.fn.tab.noConflict=function(){return t.fn.tab=n,this},t(document).on("click.zui.tab.data-api",'[data-toggle="tab"], [data-tab]',function(e){e.preventDefault(),t(this).tab("show")})}(window.jQuery),+function(t){"use strict";function e(){var t=document.createElement("bootstrap"),e={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var i in e)if(void 0!==t.style[i])return{end:e[i]};return!1}t.fn.emulateTransitionEnd=function(e){var i=!1,n=this;t(this).one("bsTransitionEnd",function(){i=!0});var o=function(){i||t(n).trigger(t.support.transition.end)};return setTimeout(o,e),this},t(function(){t.support.transition=e(),t.support.transition&&(t.event.special.bsTransitionEnd={bindType:t.support.transition.end,delegateType:t.support.transition.end,handle:function(e){if(t(e.target).is(this))return e.handleObj.handler.apply(this,arguments)}})})}(jQuery),+function(t){"use strict";var e="zui.collapse",i=function(e,n){this.$element=t(e),this.options=t.extend({},i.DEFAULTS,n),this.transitioning=null,this.options.parent&&(this.$parent=t(this.options.parent)),this.options.toggle&&this.toggle()};i.DEFAULTS={toggle:!0},i.prototype.dimension=function(){var t=this.$element.hasClass("width");return t?"width":"height"},i.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var i=t.Event("show."+e);if(this.$element.trigger(i),!i.isDefaultPrevented()){var n=this.$parent&&this.$parent.find(".in");if(n&&n.length){var o=n.data(e);if(o&&o.transitioning)return;n.collapse("hide"),o||n.data(e,null)}var a=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[a](0),this.transitioning=1;var s=function(){this.$element.removeClass("collapsing").addClass("in")[a]("auto"),this.transitioning=0,this.$element.trigger("shown."+e)};if(!t.support.transition)return s.call(this);var r=t.camelCase(["scroll",a].join("-"));this.$element.one(t.support.transition.end,s.bind(this)).emulateTransitionEnd(350)[a](this.$element[0][r])}}},i.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var i=t.Event("hide."+e);if(this.$element.trigger(i),!i.isDefaultPrevented()){var n=this.dimension();this.$element[n](this.$element[n]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var o=function(){this.transitioning=0,this.$element.trigger("hidden."+e).removeClass("collapsing").addClass("collapse")};return t.support.transition?void this.$element[n](0).one(t.support.transition.end,o.bind(this)).emulateTransitionEnd(350):o.call(this)}}},i.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var n=t.fn.collapse;t.fn.collapse=function(n){return this.each(function(){var o=t(this),a=o.data(e),s=t.extend({},i.DEFAULTS,o.data(),"object"==typeof n&&n);a||o.data(e,a=new i(this,s)),"string"==typeof n&&a[n]()})},t.fn.collapse.Constructor=i,t.fn.collapse.noConflict=function(){return t.fn.collapse=n,this},t(document).on("click."+e+".data-api","[data-toggle=collapse]",function(i){var n,o=t(this),a=o.attr("data-target")||i.preventDefault()||(n=o.attr("href"))&&n.replace(/.*(?=#[^\s]+$)/,""),s=t(a),r=s.data(e),l=r?"toggle":o.data(),c=o.attr("data-parent"),h=c&&t(c);r&&r.transitioning||(h&&h.find('[data-toggle=collapse][data-parent="'+c+'"]').not(o).addClass("collapsed"),o[s.hasClass("in")?"addClass":"removeClass"]("collapsed")),s.collapse(l)})}(window.jQuery),function(t,e){"use strict";var i=1200,n=992,o=768,a=e(t),s=function(){var t=a.width();e("html").toggleClass("screen-desktop",t>=n&&t=i).toggleClass("screen-tablet",t>=o&&t=n)},r="",l=navigator.userAgent;l.match(/(iPad|iPhone|iPod)/i)?r+=" os-ios":l.match(/android/i)?r+=" os-android":l.match(/Win/i)?r+=" os-windows":l.match(/Mac/i)?r+=" os-mac":l.match(/Linux/i)?r+=" os-linux":l.match(/X11/i)&&(r+=" os-unix"),"ontouchstart"in document.documentElement&&(r+=" is-touchable"),e("html").addClass(r),a.resize(s),s()}(window,jQuery),function(t){"use strict";var e={zh_cn:'您的浏览器版本过低,无法体验所有功能,建议升级或者更换浏览器。 了解更多...',zh_tw:'您的瀏覽器版本過低,無法體驗所有功能,建議升級或者更换瀏覽器。了解更多...',en:'Your browser is too old, it has been unable to experience the colorful internet. We strongly recommend that you upgrade a better one. Learn more...'},i=function(){for(var t=!1,e=11;e>5;e--)if(this.isIE(e)){t=e;break}this.ie=t,this.cssHelper()};i.prototype.cssHelper=function(){var e=this.ie,i=t("html");i.toggleClass("ie",e).removeClass("ie-6 ie-7 ie-8 ie-9 ie-10"),e&&i.addClass("ie-"+e).toggleClass("gt-ie-7 gte-ie-8 support-ie",e>=8).toggleClass("lte-ie-7 lt-ie-8 outdated-ie",e<8).toggleClass("gt-ie-8 gte-ie-9",e>=9).toggleClass("lte-ie-8 lt-ie-9",e<9).toggleClass("gt-ie-9 gte-ie-10",e>=10).toggleClass("lte-ie-9 lt-ie-10",e<10).toggleClass("gt-ie-10 gte-ie-11",e>=11).toggleClass("lte-ie-10 lt-ie-11",e<11)},i.prototype.tip=function(i){var n=t("#browseHappyTip");n.length||(n=t('
    '),n.prependTo("body")),i||(i=t.zui.getLangData("zui.browser",t.zui.clientLang(),e),"object"==typeof i&&(i=i.tip)),n.find(".content").html(i)},i.prototype.isIE=function(t){if(11===t)return this.isIE11();if(10===t)return this.isIE10();if(!t&&(this.isIE11()||this.isIE10()))return!0;var e=document.createElement("b");return e.innerHTML="",1===e.getElementsByTagName("i").length},i.prototype.isIE10=function(){return navigator.appVersion.indexOf("MSIE 10")!==-1},i.prototype.isIE11=function(){var t=navigator.userAgent;return t.indexOf("Trident")!==-1&&t.indexOf("rv:11")!==-1},t.zui({browser:new i}),t(function(){t("body").hasClass("disabled-browser-tip")||t.zui.browser.ie&&t.zui.browser.ie<8&&t.zui.browser.tip()})}(jQuery),function(t){"use strict";var e=864e5,i=function(t){return t instanceof Date||("number"==typeof t&&t<1e10&&(t*=1e3),t=new Date(t)),t},n=function(t){return i(t).getTime()},o=function(t,e){t=i(t),void 0===e&&(e="yyyy-MM-dd hh:mm:ss");var n={"M+":t.getMonth()+1,"d+":t.getDate(),"h+":t.getHours(),"m+":t.getMinutes(),"s+":t.getSeconds(),"q+":Math.floor((t.getMonth()+3)/3),"S+":t.getMilliseconds()};/(y+)/i.test(e)&&(e=e.replace(RegExp.$1,(t.getFullYear()+"").substr(4-RegExp.$1.length)));for(var o in n)new RegExp("("+o+")").test(e)&&(e=e.replace(RegExp.$1,1==RegExp.$1.length?n[o]:("00"+n[o]).substr((""+n[o]).length)));return e},a=function(t,e){return t.setTime(t.getTime()+e),t},s=function(t,i){return a(t,i*e)},r=function(t){return new Date(i(t).getTime())},l=function(t){return t%4===0&&t%100!==0||t%400===0},c=function(t,e){return[31,l(t)?29:28,31,30,31,30,31,31,30,31,30,31][e]},h=function(t){return c(t.getFullYear(),t.getMonth())},d=function(t){return t.setHours(0),t.setMinutes(0),t.setSeconds(0),t.setMilliseconds(0),t},u=function(t,e){var i=t.getDate();return t.setDate(1),t.setMonth(t.getMonth()+e),t.setDate(Math.min(i,h(t))),t},p=function(t,e){e=e||1;for(var i=new Date(t.getTime());i.getDay()!=e;)i=s(i,-1);return d(i)},f=function(t,e){return t.toDateString()===e.toDateString()},g=function(t,e){var i=p(t),n=s(r(i),7);return e>=i&&e1){var i;if(2==arguments.length&&"object"==typeof e)for(var n in e)void 0!==e[n]&&(i=new RegExp("({"+n+"})","g"),t=t.replace(i,e[n]));else for(var o=1;othis.length)&&(e=this.length),this.substring(e-t.length,e)===t}),String.prototype.startsWith||Object.defineProperty(String.prototype,"startsWith",{value:function(t,e){return e=!e||e<0?0:+e,this.substring(e,e+t.length)===t}}),String.prototype.includes||(String.prototype.includes=function(){return String.prototype.indexOf.apply(this,arguments)!==-1}))}(),/*! @@ -77,4 +77,4 @@ function(t){"function"==typeof define&&define.amd?define(["jquery"],t):"object"= function(t){function e(e){if("string"==typeof e.data){var i=e.handler,n=e.data.toLowerCase().split(" ");e.handler=function(e){if(this===e.target||!/textarea|select/i.test(e.target.nodeName)&&"text"!==e.target.type){var o="keypress"!==e.type&&t.hotkeys.specialKeys[e.which],a=String.fromCharCode(e.which).toLowerCase(),s="",r={};e.altKey&&"alt"!==o&&(s+="alt+"),e.ctrlKey&&"ctrl"!==o&&(s+="ctrl+"),e.metaKey&&!e.ctrlKey&&"meta"!==o&&(s+="meta+"),e.shiftKey&&"shift"!==o&&(s+="shift+"),o?r[s+o]=!0:(r[s+a]=!0,r[s+t.hotkeys.shiftNums[a]]=!0,"shift+"===s&&(r[t.hotkeys.shiftNums[a]]=!0));for(var l=0,c=n.length;l","/":"?","\\":"|"}},t.each(["keydown","keyup","keypress"],function(){t.event.special[this]={add:e}})}(jQuery),function(t,e,i){"use strict";var n="zui.picker",o={},a={width:null,lang:null,remote:null,remoteConverter:null,remoteOnly:!1,onRemoteError:null,disableEmptySearch:!1,textKey:"text",valueKey:"value",keysKey:"keys",multi:"auto",formItem:"auto",list:null,allowSingleDeselect:null,autoSelectFirst:!1,maxSelectedCount:0,maxListCount:100,hideEmptyTextOption:!0,searchValueKey:!0,emptyResultHint:null,hideOnScroll:!0,inheritFormItemClasses:!1,emptySearchResultHint:null,accurateSearchHint:null,remoteErrorHint:null,deleteByBackspace:!0,disableScrollOnShow:!0,maxDropHeight:250,dropDirection:"auto",dropWidth:"100%",maxAutoDropWidth:450,minAutoDropWidth:100,multiValueSplitter:",",multiSelectActions:5,searchDelay:200,autoClearDrop:6e4,fixLabelFor:!0,hotkey:!0,onSelect:null,onDeselect:null,onBeforeChange:null,onChange:null,onReady:null,onNoResults:null,onShowingDrop:null,onHidingDrop:null,onShowedDrop:null,onHiddenDrop:null,valueMustInList:!0},s={zh_cn:{emptyResultHint:"没有可选项",emptySearchResultHint:"没有找到 “{0}”",accurateSearchHint:"请提供更多关键词缩小匹配范围",remoteErrorHint:"无法从服务器获取结果 - {0}",selectAll:"全选",deselectAll:"取消选择"},zh_tw:{emptyResultHint:"沒有可選項",emptySearchResultHint:"沒有找到 “{0}”",accurateSearchHint:"請提供更多關鍵詞縮小匹配範圍",remoteErrorHint:"無法從服務器獲取結果 - {0}",selectAll:"全選",deselectAll:"取消選擇"},en:{emptyResultHint:"No options",emptySearchResultHint:'Cannot found "{0}"',accurateSearchHint:"Suggest to provide more keywords",remoteErrorHint:"Unable to get result from server: {0}",selectAll:"Select all",deselectAll:"Deselect all"}},r=function(o,a){var l=this;l.name=n,l.$=t(o),l.id="pk_"+(l.$.attr("id")||t.zui.uuid()),a=l.options=t.extend({},r.DEFAULTS,this.$.data(),a),void 0!==a.hideOnWindowScroll&&(a.hideOnScroll=a.hideOnWindowScroll);var c=t.zui.clientLang?t.zui.clientLang():"en",h=a.lang||c;l.lang=t.zui.getLangData?t.zui.getLangData(n,h,s):s[h]||s[c];var d,u,p=a.formItem,f='.form-item,input[type="hidden"],select,input[type="text"]';if(d="self"===p?l.$:"auto"!==p&&p?l.$.find(p):l.$.is(f)?l.$:l.$.find(f).first(),!d.length)return console.error&&console.error("Cannot found form item for picker.");if(d.is('input[type="hidden"]'))u="hidden";else if(d.is("select"))u="select";else{if(!d.is('input[type="text"]'))return console.error&&console.error("Unknown form type for picker.");u="text"}a.inheritFormItemClasses&&v.addClass(d.attr("class")),l.formType=u,l.$formItem=d.removeClass("picker").hide(),l.selfFormItem=d.is(l.$);var g=a.multi;g&&"auto"!==g||(g="select"===u&&"multiple"===d.attr("multiple")),g=!!g,l.multi=g,g||(l.options.checkable=!1);var m=a.list;m?l.setList("function"==typeof m?m({search:l.search,limit:a.maxListCount}):m,!0):"select"===u?l.updateFromSelect():l.setList([],!0);var v;v=!l.selfFormItem&&l.$.hasClass("picker")?l.$:t('
    ').insertAfter(l.$),v.addClass("picker").toggleClass("picker-multi",g).toggleClass("picker-single",!g),a.width&&v.width(a.width);var y=v.children(".picker-selections");y.length?y.empty():y=t('
    ');var b=l.id+"-search",w=t('').appendTo(y);if(!g){var x=t('
    ');a.allowSingleDeselect&&x.append(''),x.appendTo(y),l.$singleSelection=x}v.toggleClass("picker-input-empty",!w.val().length).append(y),l.$container=v,l.$selections=y,l.$search=w,l.search="";var C=a.placeholder;if(void 0===C&&(C=d.attr("placeholder")),"string"==typeof C&&C.length&&y.append(t('
    ').text(C)),a.placeholder=C,a.fixLabelFor){var k=d.attr("id");k&&t('label[for="'+k+'"]').attr("for",b)}var _=void 0!==a.defaultValue?a.defaultValue:d.val();if(null===_&&(_=""),l.setValue(_,!0),l.setDisabled(),w.on("focus",function(){l.disabled||(l._blurTimer&&(clearTimeout(l._blurTimer),l._blurTimer=0),v.addClass("picker-focus"),l.options.disableEmptySearch&&"string"==typeof l.search&&!l.search.length||l.showDropList())}).on("blur",function(){l.disabled||(l._blurTimer&&clearTimeout(l._blurTimer),l._blurTimer=setTimeout(function(){l._blurTimer=0,w.is(":focus")||v.removeClass("picker-focus")},100))}).on("input change",function(){if(!l.disabled){var t=w.val();if(g&&w.width(14*t.length),v.toggleClass("picker-input-empty",!t.length),l.tryUpdateList(t),a.disableEmptySearch){const e="string"!=typeof t||t.length;!l.dropListShowed&&e?l.showDropList():l.dropListShowed&&!e&&l.hideDropList()}}}),a.hotkey&&w.on("keydown",function(t){if(!l.disabled){var e=t.key||t.which;if(l.dropListShowed){var i=l.activeValue,n="string"==typeof i;if("Enter"===e||13===e)n&&(l.select(i,g),g?(l.$search.val(""),l.tryUpdateList("")):w.blur(),t.preventDefault(),t.stopPropagation());else if("ArrowDown"===e||40===e){var o,s=l.$activeOption;if(s&&(o=s.next(".picker-option"),g))for(;o.length&&o.hasClass("picker-option-selected");)o=o.next(".picker-option");o&&o.length||(o=l.$optionsList.children(g?".picker-option:not(.picker-option-selected)":".picker-option").first()),o.length&&l.activeOption(o),t.preventDefault(),t.stopPropagation()}else if("ArrowUp"===e||30===e){var r,s=l.$activeOption;if(s&&(r=s.prev(".picker-option"),g))for(;r.length&&r.hasClass("picker-option-selected");)r=r.prev(".picker-option");r&&r.length||(r=l.$optionsList.children(g?".picker-option:not(.picker-option-selected)":".picker-option").last()),r.length&&l.activeOption(r),t.preventDefault(),t.stopPropagation()}else"Escape"===e||27===e?l.hideDropList(!0):a.deleteByBackspace&&g&&("Backspace"===e||8===e)&&l.value&&l.value.length&&!w.val().length&&l.deselect(l.value[l.value.length-1])}}}),g){y.on("mousedown",function(t){if(!l.disabled)return l.dropListShowed&&!a.checkable?(t.preventDefault(),void t.stopPropagation()):void 0}).on("mouseup",function(e){l.disabled||y.hasClass("sortable-sorting")||t(e.target).closest(".picker-selection-remove").length||l.dropListShowed&&!a.checkable||l.focus()});var T=a.sortValuesByDnd;if(T&&t.fn.sortable){v.addClass("picker-sortable");var S={selector:".picker-selection",stopPropagation:!0,start:function(){l.hideDropList(!0)},finish:function(e){var i=[];t.each(e.list,function(t,e){i.push(e.item.data("value"))}),l.setValue(i.slice(),!1,!0)}};"object"==typeof T&&t.extend(S,T),y.sortable(S)}}if(y.on("click",".picker-selection-remove",function(e){if(!l.disabled){if(l.multi){var i=t(this).closest(".picker-selection");l.deselect(i.data("value"))}else l.deselect();e.stopPropagation()}}),d.on("chosen:updated",function(){l.updateFromSelect(!1),l.setValue(d.val(),!0),l.setDisabled(),l.updateList()}).on("chosen:activate",l.focus).on("chosen:open",l.showDropList).on("chosen:close",l.hideDropList),v.addClass("picker-ready"),t.zui.asap(function(){l.triggerEvent("ready",{picker:l},"","chosen:ready")}),!a.disableScrollOnShow){var D=a.hideOnScroll;D&&![e,i,!0].includes(D)&&t(D).on("scroll",this.handleParentScroll.bind(this))}};r.prototype.destroy=function(){var e=this,i=e.options;e.hideDropList(!0);var o=e.$search;o.off("focus blur input change"),i.hotkey&&o.off("keydown"),o.remove();var a=e.$selections;a.off("click"),e.multi&&a.off("mousedown mouseup"),a.remove();var s=e.$formItem;e.selectOptionsBackup&&(s.empty(),t.each(e.selectOptionsBackup,function(e,n){var o={value:n[i.valueKey]},a=n[i.keysKey];void 0!==a&&(o["data-"+i.keysKey]=a),s.append(t("