create-schema.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  1. const Parse = require('parse/node');
  2. // 加载环境变量(建议使用.env文件管理敏感信息)
  3. // 1. 初始化Parse连接
  4. Parse.initialize(
  5. process.env.PARSE_APP_ID || 'ncloudmaster', // 替换为你的Parse App ID
  6. process.env.PARSE_MASTER_KEY || "SnkK12*&sunq2#@20!" // 替换为你的Parse Master Key
  7. );
  8. Parse.serverURL = process.env.PARSE_SERVER_URL || 'https://server.fmode.cn/parse'; // 替换为你的Parse Server地址
  9. Parse.masterKey = process.env.PARSE_MASTER_KEY || "SnkK12*&sunq2#@20!"; // Master Key用于Schema操作
  10. // 2. 定义所有数据表结构配置(与文档范式完全对应)
  11. const tableConfigs = [
  12. // 核心租户表:Company
  13. {
  14. className: 'Company',
  15. fields: [
  16. { name: 'name', type: 'String', required: true },
  17. { name: 'corpId', type: 'String', required: false },
  18. { name: 'data', type: 'Object', required: false, defaultValue: {} },
  19. { name: 'isDeleted', type: 'Boolean', required: false, defaultValue: false }
  20. ],
  21. indexes: [
  22. { name: 'corpId_index', fields: { corpId: 1 } },
  23. { name: 'name_index', fields: { name: 1 } }
  24. ]
  25. },
  26. // 部门表:Department
  27. {
  28. className: 'Department',
  29. fields: [
  30. { name: 'name', type: 'String', required: true },
  31. { name: 'type', type: 'String', required: true, defaultValue: 'project' },
  32. { name: 'leader', type: 'Pointer', required: false, targetClass: 'Profile' },
  33. { name: 'company', type: 'Pointer', required: true, targetClass: 'Company' },
  34. { name: 'isDeleted', type: 'Boolean', required: false, defaultValue: false }
  35. ],
  36. indexes: [
  37. { name: 'company_isDeleted', fields: { company: 1, isDeleted: 1 } },
  38. { name: 'leader_index', fields: { leader: 1 } }
  39. ]
  40. },
  41. // 员工档案表:Profile
  42. {
  43. className: 'Profile',
  44. fields: [
  45. { name: 'name', type: 'String', required: true },
  46. { name: 'mobile', type: 'String', required: false },
  47. { name: 'department', type: 'Pointer', required: true, targetClass: 'Department' },
  48. { name: 'company', type: 'Pointer', required: true, targetClass: 'Company' },
  49. { name: 'userId', type: 'String', required: false },
  50. { name: 'roleName', type: 'String', required: true },
  51. { name: 'data', type: 'Object', required: false, defaultValue: {} },
  52. { name: 'isDeleted', type: 'Boolean', required: false, defaultValue: false }
  53. ],
  54. indexes: [
  55. { name: 'company_isDeleted', fields: { company: 1, isDeleted: 1 } },
  56. { name: 'userId_company', fields: { userId: 1, company: 1 }, unique: true },
  57. { name: 'roleName_company', fields: { roleName: 1, company: 1 } },
  58. { name: 'mobile_company', fields: { mobile: 1, company: 1 } }
  59. ]
  60. },
  61. // 客户信息表:ContactInfo
  62. {
  63. className: 'ContactInfo',
  64. fields: [
  65. { name: 'name', type: 'String', required: true },
  66. { name: 'mobile', type: 'String', required: false },
  67. { name: 'company', type: 'Pointer', required: true, targetClass: 'Company' },
  68. { name: 'external_userid', type: 'String', required: false },
  69. { name: 'source', type: 'String', required: false },
  70. { name: 'data', type: 'Object', required: false, defaultValue: {} },
  71. { name: 'isDeleted', type: 'Boolean', required: false, defaultValue: false }
  72. ],
  73. indexes: [
  74. { name: 'company_isDeleted', fields: { company: 1, isDeleted: 1 } },
  75. { name: 'external_userid_company', fields: { external_userid: 1, company: 1 }, unique: true },
  76. { name: 'mobile_company', fields: { mobile: 1, company: 1 } },
  77. { name: 'source_company', fields: { source: 1, company: 1 } }
  78. ]
  79. },
  80. // 企微群聊表:GroupChat
  81. {
  82. className: 'GroupChat',
  83. fields: [
  84. { name: 'chat_id', type: 'String', required: true },
  85. { name: 'name', type: 'String', required: true },
  86. { name: 'company', type: 'Pointer', required: true, targetClass: 'Company' },
  87. { name: 'project', type: 'Pointer', required: false, targetClass: 'Project' },
  88. { name: 'member_list', type: 'Array', required: false, defaultValue: [] },
  89. { name: 'joinUrl', type: 'String', required: false },
  90. { name: 'data', type: 'Object', required: false, defaultValue: {} },
  91. { name: 'isDeleted', type: 'Boolean', required: false, defaultValue: false }
  92. ],
  93. indexes: [
  94. { name: 'chat_id_company', fields: { chat_id: 1, company: 1 }, unique: true },
  95. { name: 'project_isDeleted', fields: { project: 1, isDeleted: 1 } },
  96. { name: 'company_isDeleted', fields: { company: 1, isDeleted: 1 } }
  97. ]
  98. },
  99. // 项目群组关联表:ProjectGroup
  100. {
  101. className: 'ProjectGroup',
  102. fields: [
  103. { name: 'project', type: 'Pointer', required: true, targetClass: 'Project' },
  104. { name: 'groupChat', type: 'Pointer', required: true, targetClass: 'GroupChat' },
  105. { name: 'isPrimary', type: 'Boolean', required: false, defaultValue: false }
  106. ],
  107. indexes: [
  108. { name: 'project_groupChat', fields: { project: 1, groupChat: 1 }, unique: true },
  109. { name: 'groupChat_index', fields: { groupChat: 1 } },
  110. { name: 'isPrimary_index', fields: { isPrimary: 1 } }
  111. ]
  112. },
  113. // 项目表:Project
  114. {
  115. className: 'Project',
  116. fields: [
  117. { name: 'title', type: 'String', required: true },
  118. { name: 'company', type: 'Pointer', required: true, targetClass: 'Company' },
  119. { name: 'customer', type: 'Pointer', required: true, targetClass: 'ContactInfo' },
  120. { name: 'assignee', type: 'Pointer', required: false, targetClass: 'Profile' },
  121. { name: 'status', type: 'String', required: true, defaultValue: '待分配' },
  122. { name: 'currentStage', type: 'String', required: true, defaultValue: '订单分配' },
  123. { name: 'deadline', type: 'Date', required: false },
  124. { name: 'data', type: 'Object', required: false, defaultValue: {} },
  125. { name: 'isDeleted', type: 'Boolean', required: false, defaultValue: false }
  126. ],
  127. indexes: [
  128. { name: 'company_isDeleted', fields: { company: 1, isDeleted: 1 } },
  129. { name: 'assignee_status', fields: { assignee: 1, status: 1 } },
  130. { name: 'customer_isDeleted', fields: { customer: 1, isDeleted: 1 } },
  131. { name: 'currentStage_status', fields: { currentStage: 1, status: 1 } },
  132. { name: 'deadline_index', fields: { deadline: 1 } },
  133. { name: 'updatedAt_desc', fields: { updatedAt: -1 } }
  134. ]
  135. },
  136. // 需求信息表:ProjectRequirement
  137. {
  138. className: 'ProjectRequirement',
  139. fields: [
  140. { name: 'project', type: 'Pointer', required: true, targetClass: 'Project' },
  141. { name: 'company', type: 'Pointer', required: true, targetClass: 'Company' },
  142. { name: 'spaces', type: 'Array', required: false, defaultValue: [] },
  143. { name: 'designRequirements', type: 'Object', required: false, defaultValue: {} },
  144. { name: 'materialAnalysis', type: 'Object', required: false, defaultValue: {} },
  145. { name: 'data', type: 'Object', required: false, defaultValue: {} },
  146. { name: 'isDeleted', type: 'Boolean', required: false, defaultValue: false }
  147. ],
  148. indexes: [
  149. { name: 'project_unique', fields: { project: 1 }, unique: true },
  150. { name: 'company_isDeleted', fields: { company: 1, isDeleted: 1 } }
  151. ]
  152. },
  153. // 项目团队表:ProjectTeam
  154. {
  155. className: 'ProjectTeam',
  156. fields: [
  157. { name: 'project', type: 'Pointer', required: true, targetClass: 'Project' },
  158. { name: 'profile', type: 'Pointer', required: true, targetClass: 'Profile' },
  159. { name: 'role', type: 'String', required: true },
  160. { name: 'workload', type: 'Number', required: false, defaultValue: 0 },
  161. { name: 'isDeleted', type: 'Boolean', required: false, defaultValue: false }
  162. ],
  163. indexes: [
  164. { name: 'project_isDeleted', fields: { project: 1, isDeleted: 1 } },
  165. { name: 'profile_project', fields: { profile: 1, project: 1 }, unique: true }
  166. ]
  167. },
  168. // 空间设计产品表:Product
  169. {
  170. className: 'Product',
  171. fields: [
  172. { name: 'project', type: 'Pointer', required: true, targetClass: 'Project' },
  173. { name: 'company', type: 'Pointer', required: true, targetClass: 'Company' },
  174. { name: 'profile', type: 'Pointer', required: true, targetClass: 'Profile' },
  175. { name: 'productName', type: 'String', required: true },
  176. { name: 'productType', type: 'String', required: true },
  177. { name: 'stage', type: 'String', required: true, defaultValue: 'not_started' },
  178. { name: 'processType', type: 'String', required: false },
  179. { name: 'status', type: 'String', required: true, defaultValue: 'not_started' },
  180. { name: 'fileUrl', type: 'String', required: false },
  181. { name: 'reviewStatus', type: 'String', required: true, defaultValue: 'pending' },
  182. { name: 'space', type: 'Object', required: false, defaultValue: {} },
  183. { name: 'quotation', type: 'Object', required: false, defaultValue: {} },
  184. { name: 'requirements', type: 'Object', required: false, defaultValue: {} },
  185. { name: 'reviews', type: 'Array', required: false, defaultValue: [] },
  186. { name: 'estimatedBudget', type: 'Number', required: false },
  187. { name: 'estimatedDuration', type: 'Number', required: false },
  188. { name: 'order', type: 'Number', required: false, defaultValue: 0 },
  189. { name: 'data', type: 'Object', required: false, defaultValue: {} },
  190. { name: 'isDeleted', type: 'Boolean', required: false, defaultValue: false }
  191. ],
  192. indexes: [
  193. { name: 'project_company_isDeleted', fields: { project: 1, company: 1, isDeleted: 1 } },
  194. { name: 'profile_company', fields: { profile: 1, company: 1 } },
  195. { name: 'productType_order', fields: { productType: 1, order: 1 } },
  196. { name: 'stage_status', fields: { stage: 1, status: 1 } },
  197. { name: 'reviewStatus', fields: { reviewStatus: 1 } }
  198. ]
  199. },
  200. // 项目文件表:ProjectFile
  201. {
  202. className: 'ProjectFile',
  203. fields: [
  204. { name: 'project', type: 'Pointer', required: true, targetClass: 'Project' },
  205. { name: 'product', type: 'Pointer', required: false, targetClass: 'Product' },
  206. { name: 'attach', type: 'Pointer', required: true, targetClass: 'Attachment' },
  207. { name: 'uploadedBy', type: 'Pointer', required: true, targetClass: 'Profile' },
  208. { name: 'stage', type: 'String', required: false },
  209. { name: 'category', type: 'String', required: false, defaultValue: 'other' },
  210. { name: 'data', type: 'Object', required: false, defaultValue: {} },
  211. { name: 'analysis', type: 'Object', required: false, defaultValue: {} },
  212. { name: 'isDeleted', type: 'Boolean', required: false, defaultValue: false }
  213. ],
  214. indexes: [
  215. { name: 'project_product_stage_isDeleted', fields: { project: 1, product: 1, stage: 1, isDeleted: 1 } },
  216. { name: 'attach', fields: { attach: 1 } },
  217. { name: 'uploadedBy_project', fields: { uploadedBy: 1, project: 1 } },
  218. { name: 'category_index', fields: { category: 1 } }
  219. ]
  220. },
  221. // 项目付款表:ProjectPayment ⭐
  222. {
  223. className: 'ProjectPayment',
  224. fields: [
  225. { name: 'project', type: 'Pointer', required: true, targetClass: 'Project' },
  226. { name: 'company', type: 'Pointer', required: true, targetClass: 'Company' },
  227. { name: 'type', type: 'String', required: true },
  228. { name: 'stage', type: 'String', required: true },
  229. { name: 'method', type: 'String', required: true },
  230. { name: 'amount', type: 'Number', required: true },
  231. { name: 'currency', type: 'String', required: true, defaultValue: 'CNY' },
  232. { name: 'percentage', type: 'Number', required: false, defaultValue: 0 },
  233. { name: 'paymentDate', type: 'Date', required: false },
  234. { name: 'dueDate', type: 'Date', required: true },
  235. { name: 'recordedDate', type: 'Date', required: false },
  236. { name: 'status', type: 'String', required: true, defaultValue: 'pending' },
  237. { name: 'voucherFile', type: 'Pointer', required: false, targetClass: 'ProjectFile' },
  238. { name: 'voucherUrl', type: 'String', required: false },
  239. { name: 'transactionId', type: 'String', required: false },
  240. { name: 'paymentReference', type: 'String', required: false },
  241. { name: 'paidBy', type: 'Pointer', required: true, targetClass: 'ContactInfo' },
  242. { name: 'recordedBy', type: 'Pointer', required: true, targetClass: 'Profile' },
  243. { name: 'verifiedBy', type: 'Pointer', required: false, targetClass: 'Profile' },
  244. { name: 'description', type: 'String', required: false },
  245. { name: 'notes', type: 'String', required: false },
  246. { name: 'relatedStage', type: 'String', required: false },
  247. { name: 'product', type: 'Pointer', required: false, targetClass: 'Product' },
  248. { name: 'autoReminderSent', type: 'Boolean', required: false, defaultValue: false },
  249. { name: 'reminderCount', type: 'Number', required: false, defaultValue: 0 },
  250. { name: 'data', type: 'Object', required: false, defaultValue: {} },
  251. { name: 'isDeleted', type: 'Boolean', required: false, defaultValue: false }
  252. ],
  253. indexes: [
  254. { name: 'project_company_isDeleted', fields: { project: 1, company: 1, isDeleted: 1 } },
  255. { name: 'status_stage', fields: { status: 1, stage: 1 } },
  256. { name: 'type_status', fields: { type: 1, status: 1 } },
  257. { name: 'product_productType', fields: { product: 1, productType: 1 } },
  258. { name: 'dueDate_index', fields: { dueDate: 1 } },
  259. { name: 'recordedDate_desc', fields: { recordedDate: -1 } }
  260. ]
  261. },
  262. // 客户反馈表:ProjectFeedback
  263. {
  264. className: 'ProjectFeedback',
  265. fields: [
  266. { name: 'project', type: 'Pointer', required: true, targetClass: 'Project' },
  267. { name: 'customer', type: 'Pointer', required: true, targetClass: 'ContactInfo' },
  268. { name: 'stage', type: 'String', required: true },
  269. { name: 'feedbackType', type: 'String', required: true },
  270. { name: 'content', type: 'String', required: true },
  271. { name: 'rating', type: 'Number', required: false, defaultValue: 0 },
  272. { name: 'status', type: 'String', required: true, defaultValue: '待处理' },
  273. { name: 'data', type: 'Object', required: false, defaultValue: {} },
  274. { name: 'isDeleted', type: 'Boolean', required: false, defaultValue: false }
  275. ],
  276. indexes: [
  277. { name: 'project_status_isDeleted', fields: { project: 1, status: 1, isDeleted: 1 } },
  278. { name: 'customer_project', fields: { customer: 1, project: 1 } },
  279. { name: 'feedbackType_stage', fields: { feedbackType: 1, stage: 1 } }
  280. ]
  281. },
  282. // 产品质量检查表:ProductCheck
  283. {
  284. className: 'ProductCheck',
  285. fields: [
  286. { name: 'project', type: 'Pointer', required: true, targetClass: 'Project' },
  287. { name: 'checkType', type: 'String', required: true },
  288. { name: 'checkedBy', type: 'Pointer', required: true, targetClass: 'Profile' },
  289. { name: 'checkedAt', type: 'Date', required: true },
  290. { name: 'isPassed', type: 'Boolean', required: true },
  291. { name: 'items', type: 'Array', required: false, defaultValue: [] },
  292. { name: 'data', type: 'Object', required: false, defaultValue: {} },
  293. { name: 'isDeleted', type: 'Boolean', required: false, defaultValue: false }
  294. ],
  295. indexes: [
  296. { name: 'project_checkType_isDeleted', fields: { project: 1, checkType: 1, isDeleted: 1 } },
  297. { name: 'checkedBy_isPassed', fields: { checkedBy: 1, isPassed: 1 } }
  298. ]
  299. },
  300. // 异常记录表:ProjectIssue
  301. {
  302. className: 'ProjectIssue',
  303. fields: [
  304. { name: 'project', type: 'Pointer', required: true, targetClass: 'Project' },
  305. { name: 'reportedBy', type: 'Pointer', required: true, targetClass: 'Profile' },
  306. { name: 'exceptionType', type: 'String', required: true },
  307. { name: 'severity', type: 'String', required: true },
  308. { name: 'description', type: 'String', required: true },
  309. { name: 'status', type: 'String', required: true, defaultValue: '待处理' },
  310. { name: 'resolution', type: 'String', required: false },
  311. { name: 'data', type: 'Object', required: false, defaultValue: {} },
  312. { name: 'isDeleted', type: 'Boolean', required: false, defaultValue: false }
  313. ],
  314. indexes: [
  315. { name: 'project_status_isDeleted', fields: { project: 1, status: 1, isDeleted: 1 } },
  316. { name: 'exceptionType_severity', fields: { exceptionType: 1, severity: 1 } },
  317. { name: 'reportedBy_index', fields: { reportedBy: 1 } }
  318. ]
  319. },
  320. // 跟进记录表:ContactFollow
  321. {
  322. className: 'ContactFollow',
  323. fields: [
  324. { name: 'project', type: 'Pointer', required: true, targetClass: 'Project' },
  325. { name: 'profile', type: 'Pointer', required: true, targetClass: 'Profile' },
  326. { name: 'contact', type: 'Pointer', required: true, targetClass: 'ContactInfo' },
  327. { name: 'content', type: 'String', required: true },
  328. { name: 'type', type: 'String', required: true },
  329. { name: 'stage', type: 'String', required: false },
  330. { name: 'attachments', type: 'Array', required: false, defaultValue: [] },
  331. { name: 'data', type: 'Object', required: false, defaultValue: {} },
  332. { name: 'isDeleted', type: 'Boolean', required: false, defaultValue: false }
  333. ],
  334. indexes: [
  335. { name: 'project_createdAt', fields: { project: 1, createdAt: -1 } },
  336. { name: 'profile_project', fields: { profile: 1, project: 1 } },
  337. { name: 'stage_index', fields: { stage: 1 } }
  338. ]
  339. }
  340. ];
  341. // 3. 核心工具函数:创建/更新数据表结构
  342. async function setupTableSchema(tableConfig) {
  343. const { className, fields, indexes } = tableConfig;
  344. let schema;
  345. try {
  346. // 尝试获取已有Schema
  347. schema = new Parse.Schema(className);
  348. await schema.get();
  349. console.log(`✅ 找到已有表 [${className}],开始补充缺失字段和索引`);
  350. } catch (error) {
  351. // 表不存在,创建新Schema
  352. schema = new Parse.Schema(className);
  353. console.log(`🆕 未找到表 [${className}],开始创建新表`);
  354. }
  355. // 3.1 添加/更新字段
  356. for (const field of fields) {
  357. try {
  358. // 处理Pointer类型(需特殊配置targetClass)
  359. if (field.type === 'Pointer') {
  360. schema.addPointer(field.name, field.targetClass);
  361. }
  362. // 处理普通类型
  363. else {
  364. schema.addField(field.name, field.type);
  365. }
  366. console.log(` 📝 配置字段 [${className}.${field.name}] (类型: ${field.type})`);
  367. } catch (error) {
  368. // 字段已存在时忽略错误
  369. if (error.message.includes('Field already exists')) {
  370. console.log(` ⚠️ 字段 [${className}.${field.name}] 已存在,跳过`);
  371. } else {
  372. console.error(` ❌ 配置字段 [${className}.${field.name}] 失败:`, error.message);
  373. }
  374. }
  375. }
  376. // 3.2 添加/更新索引
  377. for (const index of indexes) {
  378. try {
  379. schema.addIndex(index.name, index.fields, index.unique || false);
  380. console.log(` 🔍 配置索引 [${className}.${index.name}] (字段: ${JSON.stringify(index.fields)})`);
  381. } catch (error) {
  382. // 索引已存在时忽略错误
  383. if (error.message.includes('Index already exists')) {
  384. console.log(` ⚠️ 索引 [${className}.${index.name}] 已存在,跳过`);
  385. } else {
  386. console.error(` ❌ 配置索引 [${className}.${index.name}] 失败:`, error.message);
  387. }
  388. }
  389. }
  390. // 3.3 保存Schema变更
  391. try {
  392. await schema.save();
  393. console.log(`🎉 表 [${className}] 配置完成\n`);
  394. } catch (error) {
  395. console.error(`❌ 保存表 [${className}] 配置失败:`, error.message, '\n');
  396. }
  397. }
  398. // 4. 批量执行所有表的初始化
  399. async function initAllTables() {
  400. console.log('=====================================================');
  401. console.log('🚀 开始初始化YSS项目管理系统数据表结构');
  402. console.log(`📌 Parse Server地址: ${Parse.serverURL}`);
  403. console.log(`📌 应用ID: ${Parse.applicationId}`);
  404. console.log('=====================================================\n');
  405. // 按依赖顺序执行(先创建基础表,再创建依赖表)
  406. const dependencyOrder = [
  407. 'Company', 'Department', 'Profile', 'ContactInfo', // 基础人员表
  408. 'Project', 'ProjectRequirement', 'ProjectTeam', // 核心项目表
  409. 'GroupChat', 'ProjectGroup', // 企微关联表
  410. 'Product', 'ProjectFile', // 交付物/文件表
  411. 'ProjectSettlement', 'ProjectVoucher', // 财务表
  412. 'ProjectFeedback', 'ProductCheck', 'ProjectIssue',// 质量反馈表
  413. 'ContactFollow' // 跟进记录表
  414. ];
  415. // 按依赖顺序处理每个表
  416. for (const className of dependencyOrder) {
  417. const tableConfig = tableConfigs.find(config => config.className === className);
  418. if (tableConfig) {
  419. await setupTableSchema(tableConfig);
  420. } else {
  421. console.error(`❌ 未找到表 [${className}] 的配置,跳过\n`);
  422. }
  423. }
  424. console.log('=====================================================');
  425. console.log('🏁 所有数据表结构初始化完成!');
  426. console.log('💡 提示:请检查控制台输出,确认是否有失败的配置项');
  427. console.log('=====================================================');
  428. }
  429. // 5. 执行初始化
  430. initAllTables().catch(error => {
  431. console.error('💥 初始化过程中发生致命错误:', error.message);
  432. process.exit(1);
  433. });