project-phase-utils.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. /**
  2. * 项目阶段截止时间工具函数
  3. */
  4. /**
  5. * 默认工期配置(天数)
  6. */
  7. const DEFAULT_PHASE_DURATIONS = {
  8. modeling: 1, // 建模默认1天
  9. softDecor: 1, // 软装默认1天
  10. rendering: 1, // 渲染默认1天
  11. postProcessing: 1 // 后期默认1天
  12. };
  13. /**
  14. * 生成项目阶段截止时间
  15. * @param {Date} projectStartDate - 项目开始日期(可选)
  16. * @param {Date} projectDeadline - 项目截止日期(必填)
  17. * @param {Object} customDurations - 自定义工期(可选)
  18. * @returns {Object} phaseDeadlines对象
  19. */
  20. function generatePhaseDeadlines(projectStartDate, projectDeadline, customDurations = {}) {
  21. if (!projectDeadline) {
  22. throw new Error("项目截止日期(projectDeadline)是必填参数");
  23. }
  24. // 合并默认工期和自定义工期
  25. const durations = {
  26. ...DEFAULT_PHASE_DURATIONS,
  27. ...customDurations
  28. };
  29. const deadlineTime = projectDeadline.getTime();
  30. // 从后往前计算各阶段截止时间
  31. const postProcessingDeadline = new Date(deadlineTime);
  32. const renderingDeadline = new Date(deadlineTime - durations.postProcessing * 24 * 60 * 60 * 1000);
  33. const softDecorDeadline = new Date(deadlineTime - (durations.postProcessing + durations.rendering) * 24 * 60 * 60 * 1000);
  34. const modelingDeadline = new Date(deadlineTime - (durations.postProcessing + durations.rendering + durations.softDecor) * 24 * 60 * 60 * 1000);
  35. // 如果提供了开始日期,使用它作为建模阶段的开始时间
  36. const modelingStartDate = projectStartDate || new Date(deadlineTime - (durations.modeling + durations.softDecor + durations.rendering + durations.postProcessing) * 24 * 60 * 60 * 1000);
  37. // 构建阶段截止时间对象
  38. return {
  39. modeling: {
  40. startDate: modelingStartDate,
  41. deadline: modelingDeadline,
  42. estimatedDays: durations.modeling,
  43. status: "in_progress", // 默认第一个阶段为进行中
  44. priority: "medium"
  45. },
  46. softDecor: {
  47. startDate: modelingDeadline,
  48. deadline: softDecorDeadline,
  49. estimatedDays: durations.softDecor,
  50. status: "not_started",
  51. priority: "medium"
  52. },
  53. rendering: {
  54. startDate: softDecorDeadline,
  55. deadline: renderingDeadline,
  56. estimatedDays: durations.rendering,
  57. status: "not_started",
  58. priority: "medium"
  59. },
  60. postProcessing: {
  61. startDate: renderingDeadline,
  62. deadline: postProcessingDeadline,
  63. estimatedDays: durations.postProcessing,
  64. status: "not_started",
  65. priority: "medium"
  66. }
  67. };
  68. }
  69. /**
  70. * 从公司配置获取工期设置
  71. * @param {String} companyId - 公司ID
  72. * @returns {Promise<Object>} 工期配置
  73. */
  74. async function getCompanyPhaseDurations(companyId) {
  75. try {
  76. const companyQuery = new Parse.Query("Company");
  77. const company = await companyQuery.get(companyId, { useMasterKey: true });
  78. const companyData = company.get("data") || {};
  79. return companyData.phaseDefaultDurations || DEFAULT_PHASE_DURATIONS;
  80. } catch (error) {
  81. console.warn(`获取公司${companyId}的工期配置失败,使用默认配置:`, error.message);
  82. return DEFAULT_PHASE_DURATIONS;
  83. }
  84. }
  85. /**
  86. * 更新阶段状态
  87. * @param {String} projectId - 项目ID
  88. * @param {String} phaseName - 阶段名称 (modeling/softDecor/rendering/postProcessing)
  89. * @param {String} status - 新状态 (not_started/in_progress/completed/delayed)
  90. * @param {Object} additionalData - 额外数据(如completedAt)
  91. * @returns {Promise<Object>} 更新后的项目对象
  92. */
  93. async function updatePhaseStatus(projectId, phaseName, status, additionalData = {}) {
  94. const validPhases = ['modeling', 'softDecor', 'rendering', 'postProcessing'];
  95. const validStatuses = ['not_started', 'in_progress', 'completed', 'delayed'];
  96. if (!validPhases.includes(phaseName)) {
  97. throw new Error(`无效的阶段名称: ${phaseName}`);
  98. }
  99. if (!validStatuses.includes(status)) {
  100. throw new Error(`无效的状态: ${status}`);
  101. }
  102. const projectQuery = new Parse.Query("Project");
  103. const project = await projectQuery.get(projectId, { useMasterKey: true });
  104. const data = project.get("data") || {};
  105. const phaseDeadlines = data.phaseDeadlines || {};
  106. if (!phaseDeadlines[phaseName]) {
  107. throw new Error(`项目${projectId}没有${phaseName}阶段信息`);
  108. }
  109. // 更新阶段状态
  110. phaseDeadlines[phaseName].status = status;
  111. // 如果是完成状态,记录完成时间
  112. if (status === 'completed' && !phaseDeadlines[phaseName].completedAt) {
  113. phaseDeadlines[phaseName].completedAt = new Date();
  114. }
  115. // 合并额外数据
  116. Object.assign(phaseDeadlines[phaseName], additionalData);
  117. data.phaseDeadlines = phaseDeadlines;
  118. project.set("data", data);
  119. await project.save(null, { useMasterKey: true });
  120. return project;
  121. }
  122. /**
  123. * 获取项目当前阶段
  124. * @param {Object} phaseDeadlines - 阶段截止时间对象
  125. * @returns {String} 当前阶段名称
  126. */
  127. function getCurrentPhase(phaseDeadlines) {
  128. if (!phaseDeadlines) {
  129. return null;
  130. }
  131. const phases = ['modeling', 'softDecor', 'rendering', 'postProcessing'];
  132. // 找到第一个未完成的阶段
  133. for (const phase of phases) {
  134. const phaseInfo = phaseDeadlines[phase];
  135. if (phaseInfo && phaseInfo.status !== 'completed') {
  136. return phase;
  137. }
  138. }
  139. // 所有阶段都完成了,返回最后一个阶段
  140. return 'postProcessing';
  141. }
  142. /**
  143. * 检查阶段是否延期
  144. * @param {Object} phaseInfo - 阶段信息
  145. * @returns {Boolean} 是否延期
  146. */
  147. function isPhaseDelayed(phaseInfo) {
  148. if (!phaseInfo || !phaseInfo.deadline) {
  149. return false;
  150. }
  151. // 已完成的阶段不算延期
  152. if (phaseInfo.status === 'completed') {
  153. return false;
  154. }
  155. const deadline = new Date(phaseInfo.deadline);
  156. const now = new Date();
  157. return now > deadline;
  158. }
  159. /**
  160. * 获取阶段剩余天数
  161. * @param {Object} phaseInfo - 阶段信息
  162. * @returns {Number} 剩余天数(负数表示已逾期)
  163. */
  164. function getPhaseDaysRemaining(phaseInfo) {
  165. if (!phaseInfo || !phaseInfo.deadline) {
  166. return 0;
  167. }
  168. const deadline = new Date(phaseInfo.deadline);
  169. const now = new Date();
  170. const diff = deadline.getTime() - now.getTime();
  171. return Math.ceil(diff / (24 * 60 * 60 * 1000));
  172. }
  173. // 导出函数
  174. module.exports = {
  175. DEFAULT_PHASE_DURATIONS,
  176. generatePhaseDeadlines,
  177. getCompanyPhaseDurations,
  178. updatePhaseStatus,
  179. getCurrentPhase,
  180. isPhaseDelayed,
  181. getPhaseDaysRemaining
  182. };
  183. // Parse Cloud Code函数注册
  184. Parse.Cloud.define("generateProjectPhaseDeadlines", async (request) => {
  185. const { projectStartDate, projectDeadline, companyId, customDurations } = request.params;
  186. if (!projectDeadline) {
  187. throw new Error("缺少projectDeadline参数");
  188. }
  189. // 如果提供了companyId,获取公司的默认工期配置
  190. let durations = customDurations || {};
  191. if (companyId) {
  192. const companyDurations = await getCompanyPhaseDurations(companyId);
  193. durations = { ...companyDurations, ...customDurations };
  194. }
  195. const startDate = projectStartDate ? new Date(projectStartDate) : null;
  196. const deadline = new Date(projectDeadline);
  197. return generatePhaseDeadlines(startDate, deadline, durations);
  198. });
  199. Parse.Cloud.define("updateProjectPhaseStatus", async (request) => {
  200. const { projectId, phaseName, status, additionalData } = request.params;
  201. if (!projectId) {
  202. throw new Error("缺少projectId参数");
  203. }
  204. if (!phaseName) {
  205. throw new Error("缺少phaseName参数");
  206. }
  207. if (!status) {
  208. throw new Error("缺少status参数");
  209. }
  210. return await updatePhaseStatus(projectId, phaseName, status, additionalData);
  211. });