# 同一套进销存为什么要拆成三个行业版本:数据模型层面的三处分叉做进销存的团队大概都动摇过一个念头:既然订单—明细—库存流水这套骨架能覆盖大多数买卖场景,那是不是做成一个版本 + 几套皮肤就够了?我们把"纺织、内销、外贸"三个行业版本真正落下来以后,结论相反:分叉不在页面,而在三个很硬的地方——**数量的语义、维度属性归属哪张表、主状态机是什么**。这三样任何一样要动,都会牵到表结构和报表口径,用配置开关兜不住。下面按这三条摊开说,字段和代码都是我们自己这套实现里的真实形状。先声明边界:这是一篇纯技术复盘,文中的实体定义、字段类型和调用片段都来自我自己的实现,只用来讨论建模取舍;文中不出现任何产品或系统名称,也没有网址、入口和推广性质的内容。另外,外贸版的源码我没有读过,关于它的部分只从它对外可见的页面结构推断,不假装知道它的表设计。## 一、能复用的骨架长什么样三个版本共享的其实就四层,值得先钉清楚,后面才能说清"除此之外都不可复用"。**单据—明细—流水三层。** 单据头表存往来对象、日期、状态、金额汇总,明细表存 `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`,另有一个回收站模块负责恢复。```tsexport 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 商品表: