
很多正在做进销存选型的朋友都会遇到一个很现实的问题公司规模大了或者一个老板名下有好几家公司想用一套系统统一管采购、销售、库存和账务但每家公司的账要独立报表要分开权限不能乱串。软件销售通常会给出两个方案一是多买几套软件各管各的二是上“多公司版”听起来一个平台全搞定。真正用起来才发现有些所谓的“多公司版”只是在商品和单据上多了一个“公司”下拉框A 公司的库管随手就能看到 B 公司的采购单价月底结账时各种串账库存怎么都对不上。多公司进销存也就是多账套进销存真正的工程核心不是“界面能切换公司”而是三件事账册独立、数据隔离、权限收敛。这三件事最后都落在“账套”这个看似简单、实际设计深度非常大的概念上。这篇文章会从一个企业软件架构师的角度把一套多公司、多账套进销存系统的设计思路完整拆开账套模型怎么建、数据隔离选哪种方案、权限怎么设计、多账套下的进销存业务有哪些坑以及一套可以落地的技术实现方案。如果你正在做软件选型这份内容可以当成评审清单如果你打算自己开发或定制里面也给出了可以直接参考的代码思路。1. 多公司进销存到底解决了什么问题先别急着看功能清单先搞清楚需求本质。多公司进销存通常来自以下几类真实场景集团型公司母公司下面有多个独立法人子公司子公司各自采购、销售、管理库存但集团总部要掌握全局库存和经营数据。一老板多执照同一个实际控制人名下有几家贸易公司仓库共用、人员共用但每家公司的应收应付、成本利润必须分开核算。连锁加盟总部统一管理商品、定价、采购各加盟商或分公司独立核算库存和资金。SaaS 服务商或代账模式软件服务商要把一套系统交付给多家企业使用客户之间必须相互隔离。这里有一个容易被混淆的点“多公司”不等于“多个部门”。普通进销存软件里的“多部门”只是报表维度数据在同一个账套里流通多公司/多账套则要求数据按边界隔离跨公司的单据不能互相引用跨公司的库存不能直接调拨。再看传统方案的局限每家公司装一套软件数据彻底独立但商品档案、客户资料、供应商资料要重复维护集团想要一张汇总表得先把 Excel 收上来。维护成本极高。一套软件加“公司”字段开发起来省事但查询时只要少写一个条件A 公司的报表里就会出现 B 公司的数据。业务量一上来月底对账就是灾难。所以真正合格的“多公司进销存”不是把几家公司塞进同一套系统而是在同一个平台上做到一个账套就是一套独立账账套内的库存、往来、财务、报表自成体系。账套之间数据隔离无授权不可见、不可查、不可改。权限可按账套分配一个人可以管几个账套但在不同账套里拥有不同角色。集团层面可以看合并视角总部需要的时候又能跨账套汇总分析。小结论多账套系统设计的地基是“边界”。账套是边界数据隔离是地基权限是闸门。三者缺一不可。2. 账套、公司与组织架构到底什么关系“账套”这个词最早是从财务软件来的。传统财务软件里一套账就是一套独立的账簿体系包含科目、凭证、账簿、报表。变成进销存和财务一体化系统后账套的含义扩展到整个业务核算单元。你可以这样理解账套 一套可以独立核算的业务经营单元。它有独立的商品资料、往来单位、库存账、财务账和报表体系。而“公司”与“账套”之间的关系在不同业务模式下并不完全一样。2.1 常见模式一一个公司一个账套集团下面 5 个独立法人公司就建 5 个账套。每个账套的数据边界清晰月底各出各的报表总部再做合并。这是最多见的模式也是最推荐多数企业采用的模式。2.2 常见模式二一个公司多个账套同一个法人在不同业务线下独立核算。比如一家公司既有零售业务又有批发业务老板要求两块业务分开算利润但对外还是一个营业执照。此时可以按业务线建账套各算各的。注意这种模式下税务申报、对公账户仍是同一个法人账套只是内部管理需要。2.3 常见模式三一个账套包含多个业务组织门店数量多的连锁企业总部建一个账套门店作为仓库/部门挂在账套下面。总部能看到全部门店数据门店之间默认隔离可以通过调拨单处理商品移动。这种模式适合业务标准化程度较高的零售行业如果每个门店本身是独立法人还是建议回到“一个公司一个账套”的模式。下面这张表可以直接帮你判断自己属于哪种情况场景账套划分建议数据隔离要求典型行业集团多独立法人每个法人一个账套公司间默认不可见制造、商贸、工程同法人多业务线每个业务线一个账套仅高层可跨账套看批发零售并行连锁门店总部一个账套门店做仓库/部门门店间业务数据隔离零售、餐饮SaaS 托管多客户每个客户一个账套客户间绝对隔离代账、供应链服务小结论设计多账套系统时第一步不是画数据库表而是和企业确认账套划分规则。规则定了后面的数据模型和权限模型才有依据。3. 多账套数据隔离的三种技术方案进入技术设计前需要先做技术选型。多账套系统的数据隔离业内一般有三种方案。3.1 方案一独立数据库每个账套一个数据库实例或者一个数据库。优点隔离最彻底一个账套数据库出问题不影响其他账套备份恢复简单不容易串数据。缺点数据库数量多维护和升级成本高跨账套统计需要额外汇总程序资源占用较大。适用账套数量少、单个账套数据量大、对数据隔离非常敏感的企业。3.2 方案二共享数据库、独立 Schema同一个数据库实例里每个账套一个 Schema。优点比独立库管理方便一些隔离性仍然较强。缺点数据库实例仍然是共享的一个 Schema 的慢查询可能拖累整个实例部分数据库产品对 Schema 数量有限制。适用中型集团账套数量在几十个以内。3.3 方案三共享数据库、共享表通过账套字段隔离所有账套的数据放在同一批表里每张业务表增加 tenant_id 字段查询和写入都强制带账套条件。优点一套代码、一套表部署简单运营成本最低新增账套不需要新建库表。缺点隔离完全依赖应用层代码开发人员只要漏写一个过滤条件数据就串了测试回归压力大。适用SaaS 形态的进销存产品或者需要快速交付的多客户系统。三种方案对比对比项独立数据库共享库 独立 Schema共享表 账套字段隔离强度最强较强依赖应用层部署成本高中低扩展性一般一般最好跨账套报表困难较困难方便推荐对象大型集团中型集团SaaS、中小客户小结论从长期演进看共享表 账套字段是目前多数自研多账套系统的现实选择。但选择这条路就必须在框架层强制隔离不能靠开发人员自觉。4. 多账套核心数据模型设计从账套表到业务表无论选哪种隔离方案多账套系统的数据模型都会涉及几个核心概念账套、公司、用户、角色、业务单据。下面以共享表方案为例给出核心表结构设计思路。4.1 账套表 sys_tenantCREATE TABLE sys_tenant ( tenant_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 账套ID, tenant_code VARCHAR(64) NOT NULL COMMENT 账套编码全局唯一, tenant_name VARCHAR(128) NOT NULL COMMENT 账套名称, db_name VARCHAR(128) NULL COMMENT 独立库方案时对应的库名, status TINYINT NOT NULL DEFAULT 1 COMMENT 状态1启用 0停用, start_date DATE NULL COMMENT 启用日期, expire_date DATE NULL COMMENT 到期日期SaaS场景用, create_time DATETIME NULL, update_time DATETIME NULL );关键点tenant_code 要全局唯一。账套编码一旦确定尽量不要修改因为它会进入业务单据编号、日志和审计记录。4.2 公司/核算单元表 sys_company一个账套内可能还有一个“公司/核算单元”的维度用于报表分组。CREATE TABLE sys_company ( company_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 公司/核算单元ID, tenant_id BIGINT NOT NULL COMMENT 所属账套ID, company_code VARCHAR(64) NOT NULL COMMENT 公司编码, company_name VARCHAR(128) NOT NULL COMMENT 公司名称, parent_id BIGINT NULL COMMENT 上级公司ID支持集团层级, status TINYINT NOT NULL DEFAULT 1 COMMENT 状态1启用 0停用 );注意tenant_id 和 company_id 是两个维度。tenant_id 决定数据边界company_id 决定核算和报表分组。业务表上建议两列都有但租户隔离条件以 tenant_id 为主。4.3 用户与账套授权关系表 sys_user_tenant多账套系统的核心特点就是一个用户可以有多个账套的访问权限。CREATE TABLE sys_user ( user_id BIGINT PRIMARY KEY AUTO_INCREMENT, login_name VARCHAR(64) NOT NULL, password VARCHAR(128) NOT NULL, status TINYINT NOT NULL DEFAULT 1 ); CREATE TABLE sys_user_tenant ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, tenant_id BIGINT NOT NULL, role_id BIGINT NOT NULL COMMENT 该账套下的角色ID, is_default TINYINT NOT NULL DEFAULT 0 COMMENT 是否为默认账套1是 0否 );这里真正关键的设计是角色和账套绑定而不是和用户直接绑定。同一个用户在 A 账套是仓管员在 B 账套可能只是只读查看者。权限必须随账套切换重新加载。4.4 业务表设计示例入库单主表CREATE TABLE biz_stock_in ( id BIGINT PRIMARY KEY AUTO_INCREMENT, tenant_id BIGINT NOT NULL COMMENT 账套ID, company_id BIGINT NOT NULL COMMENT 核算单元ID, doc_no VARCHAR(32) NOT NULL COMMENT 单据编号, warehouse_id BIGINT NOT NULL COMMENT 仓库ID, supplier_id BIGINT NULL COMMENT 供应商ID, total_amount DECIMAL(18,2) NOT NULL DEFAULT 0 COMMENT 入库金额, status TINYINT NOT NULL DEFAULT 0 COMMENT 状态0草稿 1已审核 2已记账, create_by BIGINT NULL, create_time DATETIME NULL, KEY idx_tenant_doc (tenant_id, doc_no), KEY idx_tenant_company (tenant_id, company_id) );业务表的设计原则有三条tenant_id 不要全都放明细表。明细表可以通过主表 JOIN 获取账套但为了性能和数据校验方便明细表冗余 tenant_id 也是常见做法。tenant_id 要进索引。尤其要考虑(tenant_id, 业务单号)、(tenant_id, 业务日期)这样的组合索引。关键按钮要做账套二次确认。删除、审核、结账这类影响数据的操作后端必须校验当前用户对当前账套有操作权限。小结论多账套系统的数据模型核心不是把每张表做得更复杂而是把“账套维度”作为业务表最重要的天然过滤条件。5. 多账套下的权限与人员管理多公司进销存系统里权限设计比普通单账套系统复杂得多。难点不在于 RBAC基于角色的访问控制本身而在于角色和数据范围要同时受账套约束。5.1 用户与账套的关系一个正确的能力模型是这样的用户登录时选择进入哪个账套。系统记录用户最近使用的账套下次自动默认进入。用户切换账套时后端重新加载该用户在当前账套下的角色、菜单、按钮权限。用户在未授权的账套上不能通过手动修改 URL 或请求参数越权访问。5.2 三级权限体系建议把权限拆成三层第一层平台层权限。负责创建账套、分配账套资源、停用账套。平台管理员一般只做运维级操作不参与具体业务。第二层账套层权限。在某个账套内用户拥有哪些菜单和按钮权限。例如用户在 A 账套是仓库主管审批入库单在 B 账套是采购专员只能做采购订单。第三层数据范围权限。在账套之内用户还能细化到只看某个仓库、只看自己做的单据还是看全账套数据。5.3 后端权限校验必须有兜底特别要强调一点前端隐藏按钮不算权限控制。所有关键接口必须能在后端拿到当前登录人、当前账套、当前角色然后做判断。public class BizPermissionUtil { /** * 校验当前用户在当前账套下是否有指定按钮权限 * 没有权限时抛出异常由统一异常处理返回 403 */ public static void checkButtonPermission(Long tenantId, String permissionCode) { Long userId LoginUserHolder.getUserId(); // 实际项目从缓存或数据库查询user_tenant_role - role_permission boolean hasPermission PermissionService.userHasPermission( userId, tenantId, permissionCode); if (!hasPermission) { throw new ForbiddenException(当前账套下无此操作权限); } } }这里引入了一个关键概念LoginUserHolder。它是用户登录上下文配合后面的 TenantContext一起构成“当前请求是谁、在哪个账套操作”的完整上下文。小结论多账套权限设计的核心判断是角色必须挂在“用户 账套”的组合上而不是单纯挂在用户上。6. 多账套进销存的关键业务设计很多人以为多账套只是“技术问题”真正上线后才发现业务层面的设计才是最大的坑。下面这几点是实施多公司进销存系统时最容易忽略、影响却最大的地方。6.1 基础档案共享还是私有商品、客户、供应商、仓库这些基础资料在多账套下有两种策略共享商品资料所有账套用同一套商品编码适合集团统一采购、统一管理 SKU 的场景。但要注意不同账套的商品价格、成本核算方式可能是不同的。账套私有商品资料每个账套各自维护商品适合各公司经营品类差异很大的情况。更贴近实际的做法是“共享档案 账套私有扩展”。商品主数据共享但每个账套单独维护采购价、销售价、默认仓库、库存上下限。这个设计会在界面上表现为“同一商品A 账套和 B 账套看到的价格不同”。6.2 单据编号规则多账套下单据编号绝不能全局连续一个流水号否则 A 公司开单后B 公司下一张单号连在一起业务人员会非常困惑。推荐编号规则账套编码 业务类型 日期 流水号例如GC001-CG-20250126-001 GC002-CG-20250126-001实现时要注意并发问题。在多账套场景下流水号必须在数据库层面按“账套 业务类型 日期”加唯一约束否则并发开单会出现重号。6.3 库存核算与仓库维度集团多公司场景下经常出现“仓库共用、账套独立”。例如 A、B 两家公司共用同一个物理仓库但库存账要分开。解决方案有两种在仓库维度上增加“归属账套”同一个物理仓库拆成 A 仓、B 仓。系统支持“代管库存”A 账套的库存可以调拨到 B 账套双方各有一笔记录。如果共用仓库却没有账套隔离月底双方库存差异会非常难查这也是多公司进销存实施失败的高发区。6.4 财务期间与结账每个账套的财务期间可以独立设置。A 账套已经结账到 2025 年 1 月B 账套可能还在 2024 年 12 月。这意味着单据不允许反结账修改已在结账期间的账套数据必须走冲销单。集团合并报表时需要把不同账套的数据按统一期间对齐再做内部交易抵消。不要在系统里允许一个总开关“所有账套一起结账”一旦某个账套有未完成业务全部结账会卡住。6.5 往来单位余额同一客户可能在 A 账套和 B 账套都有往来。不要在账套间共享余额必须按账套分别记账。如果集团需要统计这个客户的整体应收可以做一个“客户合并视图”但并不改变各账套的独立余额。小结论多账套进销存的业务复杂度主要来自“共享与隔离”的边界。哪些数据共享、哪些数据隔离必须在蓝图阶段就确定并落到系统配置里而不是等上线后再默认全局可见。7. 技术实现示例共享表方案下的账套隔离下面给出一套可落地的技术实现思路。以 Java 技术栈为例核心是 Spring Boot MyBatis采用“共享表 账套字段”方案演示如何从登录到业务接口全程传递账套上下文并在写入数据时自动填充账套字段。7.1 定义账套上下文账套上下文的作用是在一次请求内部随时拿到“当前是哪个账套”。public class TenantContext { private static final ThreadLocalLong TENANT_HOLDER new ThreadLocal(); public static void setTenantId(Long tenantId) { TENANT_HOLDER.set(tenantId); } public static Long getTenantId() { Long tenantId TENANT_HOLDER.get(); if (tenantId null) { throw new BusinessException(未获取到当前账套请先登录或切换账套); } return tenantId; } public static void clear() { TENANT_HOLDER.remove(); } }7.2 登录后写入默认账套用户登录成功后从用户默认账套中取出 tenantId 写入上下文。如果用户切换账套则更新上下文中的值。public class LoginService { public LoginResult login(String loginName, String password) { // 1. 校验账号密码 User user userMapper.findByLoginName(loginName); if (user null || !passwordEncoder.matches(password, user.getPassword())) { throw new BusinessException(账号或密码错误); } // 2. 查询用户默认账套 UserTenant defaultTenant userTenantMapper.findDefaultByUserId(user.getUserId()); // 3. 构建登录结果返回 token同时存储账套信息 LoginResult result new LoginResult(); result.setToken(tokenService.createToken(user.getUserId())); result.setDefaultTenantId(defaultTenant.getTenantId()); result.setTenantList(userTenantMapper.findTenantListByUserId(user.getUserId())); return result; } }7.3 通过拦截器统一解析账套每次请求进来拦截器负责把请求头或登录态里的账套信息写入 TenantContext请求结束后清理。public class TenantInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 方式一第三方接口通过请求头传递 String tenantHeader request.getHeader(X-Tenant-Id); if (StringUtils.hasText(tenantHeader)) { Long tenantId Long.valueOf(tenantHeader); // 重要必须校验当前账号是否有该账套权限避免越权 if (!userService.checkTenantPermission(LoginUserHolder.getUserId(), tenantId)) { throw new ForbiddenException(当前账号无权访问该账套); } TenantContext.setTenantId(tenantId); return true; } // 方式二从登录态中取默认账套适合普通页面请求 Long defaultTenantId (Long) request.getSession() .getAttribute(DEFAULT_TENANT_ID); if (defaultTenantId ! null) { TenantContext.setTenantId(defaultTenantId); } return true; } Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { // 请求结束必须清理防止线程池复用导致账套串号 TenantContext.clear(); } }7.4 注册拦截器Configuration public class WebConfig implements WebMvcConfigurer { Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new TenantInterceptor()) .addPathPatterns(/**) .excludePathPatterns(/login, /logout); } }7.5 写入数据时自动填充账套开发人员经常忘记给实体类手动设置 tenantId解决办法是在 ORM 层做自动填充。以 MyBatis-Plus 为例可以扩展 MetaObjectHandler。Component public class TenantMetaObjectHandler implements MetaObjectHandler { Override public void insertFill(MetaObject metaObject) { Long tenantId TenantContext.getTenantId(); if (tenantId ! null metaObject.hasSetter(tenantId)) { this.strictInsertFill(metaObject, tenantId, Long.class, tenantId); } } Override public void updateFill(MetaObject metaObject) { // 租户字段一般不允许修改更新时不处理 } }7.6 查询时强制带账套条件最稳妥的做法是在实体查询基类中强制指定 tenantId或者通过 MyBatis 拦截器统一改写 SQL。MyBatis 拦截器的完整实现涉及 SQL 解析这里给出核心思路。Intercepts({ Signature(type StatementHandler.class, method prepare, args {Connection.class, Integer.class}) }) public class TenantLineInterceptor implements Interceptor { Override public Object intercept(Invocation invocation) throws Throwable { // 1. 从 invocation 中取出 BoundSql拿到原始 SQL // 2. 如果 SQL 操作的是业务表且没有 tenant_id 条件 // 3. 将 tenant_id 当前账套ID 拼入 WHERE // 4. 如果是 INSERTjoin 自动填充逻辑保证 tenant_id 有值 // 该方案依赖 SQL 解析器建议在框架层统一封装 return invocation.proceed(); } }这个拦截器不建议一开始就写容易引入 SQL 兼容问题。更现实的做法是在 Mapper XML 的公共 SQL 片段中统一写tenant_id #{currentTenantId}由代码生成器保证每张业务表的查询都带上这个条件。!-- 公共 SQL 片段新建业务表查询时统一引用 -- sql idtenantFilter tenant_id #{currentTenantId} /sql !-- 示例查询 -- select idpageStockIn resultTypeStockInVO SELECT * FROM biz_stock_in WHERE include refidtenantFilter/ AND doc_no LIKE CONCAT(%, #{keyword}, %) ORDER BY create_time DESC /select小结论共享表方案的成败在于技术框架是否强行拦住“漏条件”。拦截器、自动填充、代码生成器这三样至少要落地两样才能让多账套隔离真正可靠。8. 多账套功能的验证路径与上线检查写完了代码怎么证明多账套功能是真正可用的建议按照下面的路径做一次最小验证。8.1 最小验证场景创建两个账套账套 AGC001和账套 BGC002。创建用户 U并分别授权 A、B 两个账套角色都设为“仓库管理员”。用 U 登录进入账套 A新增一张入库单仓库选择“A 仓”商品数量为 10。切换到账套 B录入同样的商品仓库选择“B 仓”商品数量为 5。分别查看两个账套的库存汇总A 显示 10B 显示 5。8.2 数据库验证语句登录数据库执行下面这条 SQL可以看到两个账套的数据各归各的SELECT tenant_id, company_id, COUNT(*) AS doc_count FROM biz_stock_in GROUP BY tenant_id, company_id;预期结果tenant_id | company_id | doc_count ----------------------------------- 1001 | 2001 | 1 1002 | 2002 | 18.3 接口越权验证这是最容易忽略的一步。登录账套 A拿到请求 Token。构造一个带账套 B 的请求比如请求头设置X-Tenant-Id: 1002。系统应该返回 403 无权限而不是返回账套 B 的数据。如果这一步没有拦截说明你的后端接口只做了表面上的条件过滤没有做真实权限校验这是一个严重的越权漏洞。8.4 上线检查清单每个业务表是否都有 tenant_id并且索引合理。新增数据时 tenant_id 是否自动填充。查询列表时是否强制带账套过滤条件。用户切换账套后菜单和按钮权限是否重新加载。批量导入、定时任务、报表模块是否都带上了账套维度。跨账套操作是否都通过审批或管理层权限而不是普通用户可执行。小结论多账套系统的验证不是测一遍“能用”而是要专门验证“边界”。验证时多问一句我能不能想办法看到另一个账套的数据如果能就说明系统不合格。9. 多账套进销存常见问题与排查方法上线后出现数据串账、单据重复等问题时不要盲目改数据先按下面表格定位根因。问题现象可能原因排查方式解决方案A 账套能看到 B 账套的单据查询 SQL 漏写 tenant_id 条件查看 SQL 日志检查是否只有 company_id 过滤统一在 SQL 中强制拼接 tenant_id新增单据未写入 tenant_idORM 自动填充未生效查看 insert 语句检查实体类是否有 tenantId 字段实现 MetaObjectHandler 自动填充用户切换账套后权限混乱切换后未重新加载角色菜单查看切换接口是否重新查询 user_tenant_role切账套时重新初始化权限上下文两个账套单据编号重复流水号未按账套维度区分检查单据编号生成逻辑按“账套 类型 日期”加唯一约束同一商品在两个账套价格冲突共享商品档案但价格未按账套隔离检查商品价格表是否有 tenant_id价格表增加账套维度合并报表不平账套间内部交易未抵消核对内部调拨/内部销售单据建立内部交易抵消规则定时任务处理了全量账套数据任务方法没取 TenantContext查看任务日志中 tenant_id 为空定时任务按账套列表循环处理或显式指定账套小结论多账套系统 90% 以上的数据异常根因都是“上下文没传对”或“查询条件没写全”。排查时优先看 SQL 日志里的 tenant_id 值。10. 项目选型与实施最佳实践最后一部分给正在选型或准备自研的团队一些偏工程化的建议。10.1 自研还是外购如果账套数量只有两三个且业务比较简单不建议自研。市面上的成熟进销存系统加上多账套模块实施成本远低于从零开发。如果属于以下情况可以考虑自研或深度定制有特殊行业规则无法用标准软件覆盖。账套数量多需要做 SaaS 化运营。集团有复杂的合并报表、内部交易抵消需求。10.2 实施顺序建议先定账套边界把公司清单、账套清单、共享档案范围确定下来。再定权限模型列清楚每个用户在哪些账套、充当什么角色。初始化基础档案商品、客户、供应商、仓库统一编码。导入期初数据期初库存、期初应收应付注意按账套分别导入。试运行和月结演练用真实业务跑一个月重点检查跨账套报表和结账流程。10.3 安全与运维账套创建、停用、删除必须走审批流程。生产环境建议遵循最小权限原则普通 DBA 不直接改业务数据。定期备份要按账套维度验证恢复能力很多小公司备份了但没试过恢复。上线初期建议每天跑一次“账套数据汇总比对”及时发现串账苗头。小结论多账套系统的真正成本在实施阶段。账套边界、基础档案、权限模型这三件事想清楚上线就顺想不清楚后面全是补丁式修复。11. 最后说一点实在的回到最开始的问题多公司进销存软件到底怎么选、怎么设计我的建议是不管买软件还是自研都先向对方或自己团队确认三个核心问题账套与公司是什么关系一个公司一个账套还是按业务线拆账套。数据隔离是怎么实现的是独立库、独立 Schema还是共享表加账套字段。如果是共享表有没有框架层强制隔离。切换账套后权限是否重新加载一个用户在不同账套是否可以拥有不同角色。这三个问题能回答清楚这套多账套进销存系统的基本盘就稳了。多账套系统最怕的不是功能少而是边界模糊。边界一旦模糊后面每一次加需求、每一次手工修复数据都是在给系统埋雷。如果你正在做选型可以拿这篇文章里的验证路径去测试候选软件如果你准备自己开发建议先用最小原型跑通“账套上下文 拦截器 自动填充”这条链路再扩展业务模块。先把边界立住后面再慢慢加功能。