☰
同一套进销存为什么要拆成三个行业版本:数据模型层面的三处分叉
2026/10/25 1:52:06 网站建设 项目流程

# 同一套进销存为什么要拆成三个行业版本:数据模型层面的三处分叉

做进销存的团队大概都动摇过一个念头:既然订单—明细—库存流水这套骨架能覆盖大多数买卖场景,那是不是做成一个版本 + 几套皮肤就够了?

我们把"纺织、内销、外贸"三个行业版本真正落下来以后,结论相反:分叉不在页面,而在三个很硬的地方——**数量的语义、维度属性归属哪张表、主状态机是什么**。这三样任何一样要动,都会牵到表结构和报表口径,用配置开关兜不住。下面按这三条摊开说,字段和代码都是我们自己这套实现里的真实形状。

先声明边界:这是一篇纯技术复盘,文中的实体定义、字段类型和调用片段都来自我自己的实现,只用来讨论建模取舍;文中不出现任何产品或系统名称,也没有网址、入口和推广性质的内容。另外,外贸版的源码我没有读过,关于它的部分只从它对外可见的页面结构推断,不假装知道它的表设计。

## 一、能复用的骨架长什么样

三个版本共享的其实就四层,值得先钉清楚,后面才能说清"除此之外都不可复用"。

**单据—明细—流水三层。** 单据头表存往来对象、日期、状态、金额汇总,明细表存 `productId / productName / quantity / price / total`,库存变动另落一张流水表:

```ts
@Entity('inventory_records')
export class InventoryRecord extends BaseEntity {
  @Column() userId: number;
  @Column() productId: number;
  @Column({ type: 'varchar', length: 20 }) type: string; // in / out / adjust
  @Column('int') quantity: number;                        // 正数增,负数减
  @Column('int', { default: 0 }) beforeStock: number;
  @Column('int', { default: 0 }) afterStock: number;
  @Column({ nullable: true }) warehouseId: number;
}
```

**行级多租户。** 业务表都带 `userId`,同一套库、同一批表,靠 `@Index('idx_products_user_id')` 这类索引做隔离,没有分库分租户。好处是运维面只有一套 schema,代价是所有"清理类"接口必须自己遍历表——这也是后面精度问题会二次出现的地方。

**软删 + 回收站。** 公共基类带 `DeleteDateColumn`,业务删除默认只写 `deleted_at`,另有一个回收站模块负责恢复。

```ts
export abstract class BaseEntity {
  @PrimaryGeneratedColumn() id: number;
  @CreateDateColumn({ name: 'created_at' }) createdAt: Date;
  @UpdateDateColumn({ name: 'updated_at' }) updatedAt: Date;
  @DeleteDateColumn({ name: 'deleted_at' }) deletedAt?: Date;
}
```

**明细行冗余快照。** 明细表把 `productName / color / batchNo / colorCode / unit` 都复制了一份,而不是每次去 join 商品表:

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询