{"record":{"id":"a5403c84d1b8cc25","repo":"nocobase/nocobase","slug":"invalid-main-department-it-must-be-one-of-the-use","errorCode":null,"errorMessage":"Invalid main department, it must be one of the user's departments","messagePattern":"Invalid main department, it must be one of the user's departments","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/plugins/@nocobase/plugin-departments/src/server/plugin.ts","lineNumber":142,"sourceCode":"            userId: userId,\n            departmentId: mainDepartmentId,\n          },\n          transaction,\n        });\n        if (userDepartment) {\n          return;\n        }\n      }\n      if (Array.isArray(submittedDepartments)) {\n        const included = submittedDepartments.some((d) => {\n          const id = typeof d === 'object' ? d && (d.id ?? d) : d;\n          return `${id}` === `${mainDepartmentId}`;\n        });\n        if (included) {\n          return;\n        }\n      }\n      throw new Error(`Invalid main department, it must be one of the user's departments`);\n    });\n\n    this.app.on('beforeSignOut', ({ userId }) => {\n      this.app.cache.del(`departments:${userId}`);\n    });\n\n    const userDataSyncPlugin = this.app.pm.get('user-data-sync') as PluginUserDataSyncServer;\n    if (userDataSyncPlugin && userDataSyncPlugin.enabled) {\n      userDataSyncPlugin.resourceManager.registerResource(new DepartmentDataSyncResource(this.db, this.app.logger), {\n        // write department records after writing user records\n        after: 'users',\n      });\n    }\n  }\n\n  async install(options?: InstallOptions) {\n    const collectionRepo = this.db.getRepository<any>('collections');\n    if (collectionRepo) {","sourceCodeStart":124,"sourceCodeEnd":160,"githubUrl":"https://github.com/nocobase/nocobase/blob/fa42722fefe44265490dff2c27d79e2882bce4fa/packages/plugins/@nocobase/plugin-departments/src/server/plugin.ts#L124-L160","documentation":"The departments plugin registers a beforeSignIn handler that verifies a user's chosen main department (mainDepartmentId) actually belongs to the user's own department list; the users table may be edited directly or through non-UI flows, so this hook re-checks at sign-in load time. If the main department is not among the user's departments, it throws 'Invalid main department, it must be one of the user's departments' to keep user↔department consistency.","triggerScenarios":"Signing in (beforeSignIn event) when the user record's mainDepartmentId references a department the user is not a member of — e.g. the user was removed from their main department without clearing mainDepartmentId, or mainDepartmentId was set to a department via direct DB/import updates.","commonSituations":"Admin removed the user from a department but the main-department pointer stayed; data import/sync wrote mainDepartmentId without memberships; a bug or manual DB edit left a stale id; deleting/reassigning departments out from under users.","solutions":["Update the user: set their main department to one they actually belong to (via the users/departments admin UI)","Add the user back to the department referenced by mainDepartmentId, or clear mainDepartmentId (set to null) to fall back to the default","Fix data directly: SELECT memberships for the user and align mainDepartmentId with one of them before retrying sign-in","Audit import/sync scripts to always write memberships and mainDepartmentId atomically"],"exampleFix":"// before (stale data)\nuser.mainDepartmentId = 12; // user no longer member of dept 12\n// after\ndb.getRepository('users').update({ id: user.id, mainDepartmentId: null }); // or set to a department the user belongs to","handlingStrategy":"validation","validationCode":"const memberships = await db.getRepository('departments_users').find({ filter: { userId } });\nconst deptIds = memberships.map((m) => String(m.departmentId));\nif (user.mainDepartmentId && !deptIds.includes(String(user.mainDepartmentId))) {\n  await db.getRepository('users').update({\n    filterByTk: user.id,\n    values: { mainDepartmentId: deptIds[0] ?? null },\n  });\n}","typeGuard":"function hasValidMainDepartment(user: { mainDepartmentId: number | null; departments?: Array<{ id: number }> }): boolean {\n  return user.mainDepartmentId == null || !!user.departments?.some((d) => String(d.id) === String(user.mainDepartmentId));\n}","tryCatchPattern":"try {\n  await signIn(user);\n} catch (err) {\n  if (/Invalid main department/.test(err.message)) {\n    await resetMainDepartment(user.id); // null it out or pick a real membership\n    await signIn(user);\n  } else {\n    throw err;\n  }\n}","preventionTips":["Always clear/update mainDepartmentId when removing a user from a department","Run a periodic integrity check joining users.mainDepartmentId against departments_users","Make import/sync jobs set memberships and main department in one transaction"],"tags":["departments","users","data-integrity","sign-in","server"],"backgroundTag":"invalid-reference-data","analyzedSha":"fa42722fefe44265490dff2c27d79e2882bce4fa","analyzedAt":"2026-09-01T00:54:31.202Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}