《独立开发者的商业指南·资本与退出》9.1 雇佣与外包合同的承继

股权收购下合同主体不变、自动承继,资产收购下每份合同都要重签或取得同意。本节用 Nord 的 10 份咨询合同、90 家产品客户与 3 家供应商,算清 85 小时重签、9,725 美元承继成本,以及控制权变更条款暴露的 75,744 美元收入,并对照 LedgerLite 的 260 份订阅合同。

以下情境为教学假设,人物与数字不代表经核实的真实案例。

交割清单上,域名、代码、账号都能列成条目;合同不行。合同不是一件可以装箱的资产,而是一段关系:对手方愿不愿意继续、在什么主体下继续、按什么条款继续。买方付的钱里,很大一部分买的不是服务器,而是这段关系能否在交割日之后原样存在。Nord 的年收入 154,320 美元里,72.7% 来自咨询服务,前 3 家客户占 48%;这些数字在估值表上是收入,在交割日是 105 份要处理的合同。

本节只回答一个问题:把合同从卖方手里交到买方手里,要花多少小时、多少钱、暴露多少收入。它不提供法律意见,也不判断某一条款在某个司法辖区是否有效;所有工时、比例与同意成本都是教学假设。

两种交易结构,两套承继规则

股权收购买的是主体。买方买下持有合同的公司的股份,合同对手方在法律上没有变化:劳动合同继续由原雇主履行,供应商继续向原公司开票,客户继续向原公司付款。承继是默认结果,不需要逐份重签。买方要担心的不是「合同要不要换」,而是「合同里有没有控制权变更条款」。

资产收购买的是资产。买方新设主体,把代码、客户关系、商标、设备买过去,但合同不会自动跟着走。每一份合同都要处理:能转让的签转让协议,不能转让的取得对方同意,对方不同意的重新签,重签也签不下来的,这份收入就留在卖方那里。资产收购的尽调清单里,合同数量不是背景信息,而是工作量。

维度股权收购资产收购
合同主体不变变为买方新主体
承继方式默认承继逐份转让、同意或重签
员工合同随主体继续通常要新雇主重新签约
供应商合同随主体继续常含禁止转让条款,需同意
客户合同随主体继续按合同转让条款逐份处理
主要风险控制权变更触发终止同意拿不到、重签期客户流失

这张表解释了一个常见误判:卖方以为「股权收购更省事」,买方却可能更愿意做资产收购,因为资产收购可以把某些负债留在旧主体。结构选择不是谁更先进,而是谁承担哪一类成本和风险。合同承继成本只是其中一项,却经常是交割后第一个月最直观的一项。

三类合同:劳动、供应商、客户

Nord 只有两名合伙人,没有正式雇员,但有劳动合同或合伙人协议;外包与工具供应商构成第二类;咨询合同和产品订阅构成第三类。合同份数的教学假设是:2 份劳动/合伙人协议、3 份供应商合同、10 份咨询合同、90 份产品订阅;90 家产品客户来自案例事实表,10 份咨询合同按 110 小时/月、每客户每月 11 小时反推。三类合同的承继逻辑不同,不能用一张「合同总数」的表格糊过去。

劳动合同承继的不只是工资,还有工龄、假期、通知期和可能的补偿。股权收购下,雇主主体不变,合同通常继续;资产收购下,新雇主需要重新签约,员工可以选择不签。对两人合伙的 Nord,这个问题更尖锐:合伙人既是股东又是劳动者,买方买的如果是资产,两位合伙人要不要变成买方的员工,是一个留任问题,不只是合同问题。

供应商合同承继的是连续性和价格。Nord 的直接成本里有 18,000 美元外包和 6,000 美元工具;供应商合同通常写着「未经书面同意不得转让」,有的还写着「控制权变更视为转让」。工具类供应商可能按账号绑定,换主体要重新验证付款方式;外包供应商则可能因为换了付款方而要求重谈价格。供应商合同金额不大,但断供的后果不小。

客户合同承继的是收入和信任。Nord 的 10 份咨询合同按项目或按月签,平均每份年收入 11,220 美元;90 家产品客户按月付 39 美元,平均每份年收入 468 美元。咨询合同金额大、数量少、条款重;产品订阅金额小、数量多、条款标准化。两类合同的承继工作量不能按同一套流程估。

合同类型数量单份年收入承继难点谁最可能拖慢
劳动/合伙人2不适用工龄、留任、角色合伙人本人
供应商3不适用禁转条款、付款主体供应商财务与法务
咨询客户1011,220人手、交付责任、同意客户采购与业务负责人
产品客户90468条款通知、支付授权客户管理员

Nord 的合同集中度让第三类更危险:前 3 家客户占收入 48%,如果这 3 家都在 10 份咨询合同里,那么咨询合同的单份平均收入会被拉高,重签时任何一家掉队都会直接改变收入结构。数量最多的 90 家产品客户反而好处理,因为单份金额小、条款标准化,适合批量通知。

重签工作量:105 份合同、85 小时、9,725 美元

把重签工作量拆成「合同数 × 单份工时 × 时薪」,可以得到一个可检验的预算。Nord 的教学假设是:每份劳动/合伙人合同 2 小时,每份供应商合同 2 小时,每份咨询合同 3 小时,每份产品订阅 0.5 小时;创始人时薪按咨询费率 85 美元计。

合同组数量单份工时总工时按 85 美元计
劳动/合伙人22.04.0340
供应商32.06.0510
咨询客户103.030.02,550
产品客户900.545.03,825
合计10585.07,225

85 小时按每天 4 小时计,是 21 个工作日;按 Nord 每月 110 小时的咨询产能计,是 0.77 个月。也就是说,仅重签合同就会吃掉创始人近一个月的可计费时间,还不包括谈判、等待对方回签和客户因为换主体而提出的额外问题。

除了重签,还有同意成本。10 份咨询合同里有「未经书面同意不得转让」的条款,教学假设每份同意的法律与沟通成本是 250 美元,合计 2,500 美元。资产收购下,合同承继总成本是 7,225 + 2,500 = 9,725 美元,占年收入 154,320 美元的 6.3%,即 630 个基点。

股权收购下,重签工时为 0,只剩同意成本 2,500 美元,占年收入的 1.6%,即 162 个基点。两种结构的差额是 7,225 美元、468 个基点。这个差额不是「股权收购更便宜」的结论,而是「资产收购把合同工作显性化」的度量:买方若坚持资产收购,这 9,725 美元要么由卖方承担,要么从价格里扣。

控制权变更条款:合同在交割日自动终止

承继不等于安全。很多合同里有一条控制权变更条款:当一方的大部分股权、投票权或实际控制人发生变化时,另一方有权终止合同,或者要求重新谈判。股权收购下主体没变,但控制权变了,这条款照样触发。卖方在尽调表上写「合同随主体自动承继」,买方法务会追问一句:「控制权变更条款查了吗?」

Nord 的教学假设是:10 份咨询合同里 60% 含控制权变更终止权,90 份产品订阅里 20% 含同类条款。按数量算,暴露合同是 6 + 18 = 24 份;按收入算,咨询部分 67,320 美元、产品部分 8,424 美元,合计 75,744 美元,占年收入 49.1%。

合同组数量含控制权变更比例暴露合同暴露收入
咨询客户1060%667,320
产品客户9020%188,424
合计1002475,744

49.1% 的收入暴露不是「一定会丢」,而是「交割日之后对方有权重新选择」。买方会把这份不确定性写进价格调整、托管或 earn-out 条件;卖方则要在交割前拿到续签或放弃终止权的确认函。确认函拿得越晚,买方扣得越多。

转让条款和控制权变更条款是两件事。转让条款管的是「合同能不能换主体」,控制权变更条款管的是「主体没换但老板换了,对方能不能走」。资产收购要先过转让关,再过控制权变更关;股权收购只过第二关,但第二关往往被忽略,因为卖方以为「主体没变就没事」。

LedgerLite 的 260 份订阅合同是另一种量级:股权收购下合同随新加坡主体继续,资产收购则要逐份换主体。260 份按每份 0.2 小时的教学假设计是 52 小时,按 85 美元计是 4,420 美元;这还没算需要单独同意的企业合同。Tidecast 则提醒另一个边界:Steam 账号不是合同,平台不允许过户,买方拿到的是代码与发行权,不是店铺本身。

写成代码:contract-assignment.ts

下面的 contract-assignment.ts 把承继拆成四个量:要重签的合同数、重签工时与成本、同意成本、控制权变更暴露的收入。资产收购下 contractsToRepaper 等于合同总数,股权收购下为 0;两种结构都计算 atRiskRevenueCents,因为控制权变更条款不随结构消失。

// file: src/capital/contract-assignment.ts
// tier: L1
// verify: node --test test/capital/contract-assignment.test.ts

import { type Cents } from "../core/money.ts";
import { type Bps, divide } from "../core/rate.ts";
import { type Result, fail, ok } from "../core/result.ts";

export type DealStructure = "stock" | "asset";

export type ContractKind = "employment" | "supplier" | "customer";

export const CONTRACT_KINDS: ContractKind[] = [
  "employment",
  "supplier",
  "customer",
];

export type ContractGroup = {
  kind: ContractKind;
  count: number;
  annualRevenueCents: Cents;
  reSignHours: number;
  consentRequired: boolean;
  consentCostCents: Cents;
  changeOfControlBps: Bps;
};

export type AssignmentInput = {
  structure: DealStructure;
  hourlyRateCents: Cents;
  groups: ContractGroup[];
};

export type AssignmentOutcome = {
  structure: DealStructure;
  automaticSuccession: boolean;
  contractCount: number;
  contractsToRepaper: number;
  contractsWithConsent: number;
  reSignHours: number;
  reSignCostCents: Cents;
  consentCostCents: Cents;
  totalCostCents: Cents;
  annualRevenueCents: Cents;
  totalCostBpsOfRevenue: Bps;
  atRiskContracts: number;
  atRiskRevenueCents: Cents;
  atRiskRevenueBps: Bps;
};

function isKind(value: string): value is ContractKind {
  return (CONTRACT_KINDS as string[]).includes(value);
}

function checkAmount(value: Cents, label: string): string | null {
  if (!Number.isInteger(value) || value < 0) {
    return `${label} must be a non-negative integer amount in cents`;
  }
  return null;
}

function checkBps(value: Bps, label: string): string | null {
  if (!Number.isInteger(value) || value < 0 || value > 10000) {
    return `${label} must be between 0 and 10000 bps`;
  }
  return null;
}

/**
 * A stock deal changes the owner of the contracting entity, so an employment,
 * supplier or customer contract continues with the same legal party. An asset
 * deal moves the contract to a new entity, so every contract must be re-papered
 * or transferred with consent. Separately, a change-of-control clause gives the
 * counterparty a termination right even when the entity itself survives. This
 * model counts both costs and the revenue exposed to that right. It is a
 * teaching model, not legal advice.
 */
export function assessAssignment(
  input: AssignmentInput,
): Result<AssignmentOutcome> {
  if (input.structure !== "stock" && input.structure !== "asset") {
    return fail("structure must be stock or asset");
  }
  if (!Number.isInteger(input.hourlyRateCents) || input.hourlyRateCents <= 0) {
    return fail("hourly rate must be a positive integer amount in cents");
  }
  if (!Array.isArray(input.groups) || input.groups.length === 0) {
    return fail("at least one contract group is required");
  }

  let contractCount = 0;
  let contractsWithConsent = 0;
  let reSignHours = 0;
  let reSignCostCents = 0;
  let consentCostCents = 0;
  let annualRevenueCents = 0;
  let atRiskContracts = 0;
  let atRiskRevenueCents = 0;

  for (const group of input.groups) {
    if (!isKind(group.kind)) {
      return fail("contract kind must be employment, supplier or customer");
    }
    if (!Number.isInteger(group.count) || group.count < 0) {
      return fail("contract count must be a non-negative integer");
    }
    if (
      !Number.isFinite(group.reSignHours) ||
      group.reSignHours < 0 ||
      group.reSignHours > 1000
    ) {
      return fail("re-sign hours must be between 0 and 1000 per contract");
    }
    for (const [value, label] of [
      [group.annualRevenueCents, "annual revenue"],
      [group.consentCostCents, "consent cost"],
    ] as Array<[Cents, string]>) {
      const error = checkAmount(value, label);
      if (error) {
        return fail(error);
      }
    }
    const bpsError = checkBps(
      group.changeOfControlBps,
      "change-of-control probability",
    );
    if (bpsError) {
      return fail(bpsError);
    }

    contractCount += group.count;
    annualRevenueCents += group.count * group.annualRevenueCents;
    if (group.consentRequired) {
      contractsWithConsent += group.count;
      consentCostCents += group.count * group.consentCostCents;
    }
    if (input.structure === "asset") {
      reSignHours += group.count * group.reSignHours;
    }
    atRiskContracts += Math.round(
      (group.count * group.changeOfControlBps) / 10000,
    );
    atRiskRevenueCents += Math.round(
      (group.count * group.annualRevenueCents * group.changeOfControlBps) /
        10000,
    );
  }

  if (contractCount === 0) {
    return fail("contract groups must describe at least one contract");
  }
  if (input.structure === "asset") {
    reSignCostCents = Math.round(reSignHours * input.hourlyRateCents);
  }
  const totalCostCents = reSignCostCents + consentCostCents;

  return ok({
    structure: input.structure,
    automaticSuccession: input.structure === "stock",
    contractCount,
    contractsToRepaper: input.structure === "asset" ? contractCount : 0,
    contractsWithConsent,
    reSignHours,
    reSignCostCents,
    consentCostCents,
    totalCostCents,
    annualRevenueCents,
    totalCostBpsOfRevenue: divide(totalCostCents, annualRevenueCents),
    atRiskContracts,
    atRiskRevenueCents,
    atRiskRevenueBps: divide(atRiskRevenueCents, annualRevenueCents),
  });
}

export type AssignmentComparison = {
  cheaper: DealStructure | "equal";
  deltaCostCents: Cents;
  deltaCostBpsOfRevenue: Bps;
  sameAtRiskRevenue: boolean;
};

/**
 * Compares the two structures on contract work only. It deliberately ignores
 * price, tax basis, entity setup and the buyer's appetite for inherited
 * liabilities, because those belong to the deal-structure model. The point of
 * this comparison is narrow: how much does it cost to keep the same revenue
 * attached to a contracting party after closing?
 */
export function compareAssignment(
  asset: AssignmentOutcome,
  stock: AssignmentOutcome,
): Result<AssignmentComparison> {
  if (asset.structure !== "asset" || stock.structure !== "stock") {
    return fail("expected one asset outcome and one stock outcome");
  }
  if (asset.contractCount !== stock.contractCount) {
    return fail("both structures must describe the same contract base");
  }
  if (asset.annualRevenueCents !== stock.annualRevenueCents) {
    return fail("both structures must describe the same revenue base");
  }

  const deltaCostCents = asset.totalCostCents - stock.totalCostCents;
  const cheaper =
    deltaCostCents < 0 ? "asset" : deltaCostCents > 0 ? "stock" : "equal";

  return ok({
    cheaper,
    deltaCostCents,
    deltaCostBpsOfRevenue: divide(
      Math.abs(deltaCostCents),
      asset.annualRevenueCents,
    ),
    sameAtRiskRevenue:
      asset.atRiskRevenueCents === stock.atRiskRevenueCents,
  });
}

代码把「重签」和「控制权变更」分开,是因为它们的成本性质不同:重签是确定要花的工时,控制权变更是概率性的收入暴露。totalCostCents 只加确定成本,atRiskRevenueCents 单独列出,避免把 75,744 美元的暴露当成 75,744 美元的损失。compareAssignment 只比较合同工作,不比较价格、税基和负债承接;那些属于交易结构模型,见 7.1 资产收购与股权收购 。

把 Nord 代入资产收购:105 份合同、重签 85 小时、重签成本 722,500 美分、同意成本 250,000 美分、总成本 972,500 美分,占年收入 630 个基点;控制权变更暴露 24 份合同、7,574,400 美分,占 4,908 个基点。换成股权收购:重签 0 小时、总成本 250,000 美分、占 162 个基点,暴露收入不变。差额 722,500 美分、468 个基点,就是资产收购多出来的合同工作。

小结

  • 股权收购买主体,合同默认承继;资产收购买资产,合同要逐份转让、取得同意或重签。
  • 三类合同要分开算:劳动/合伙人合同看工龄与角色,供应商合同看禁转与付款主体,客户合同看金额与条款。
  • Nord 的 105 份合同按教学工时算是 85 小时、7,225 美元;加 2,500 美元同意成本,资产收购承继成本 9,725 美元,占年收入 6.3%。
  • 股权收购下重签工时为 0,只剩 2,500 美元同意成本;两种结构差额 7,225 美元、468 个基点。
  • 控制权变更条款不随结构消失:Nord 教学假设下 24 份合同、75,744 美元收入暴露,占年收入 49.1%。
  • 转让条款管「能不能换主体」,控制权变更条款管「主体没换但老板换了能不能走」,资产收购要过两关,股权收购只过第二关。
  • LedgerLite 的 260 份订阅合同在股权收购下随主体继续,资产收购则要逐份换主体;Tidecast 的 Steam 账号不是合同,不能按合同承继处理。

阅读导航:上一节:8.3 代码、域名与数据交割 · 下一节:9.2 竞业、保密与留任 。

常见错误与边界

  1. 把「股权收购」当成「合同不用管」。 主体不变只解决承继,不解决控制权变更条款;后者照样能让对方在交割日选择离开。
  2. 把合同总数当工作量。 105 份合同里,90 份产品订阅每份 0.5 小时,10 份咨询每份 3 小时;按平均工时乘总数会低估咨询合同。
  3. 忽略同意成本。 重签工时之外,还有征求同意、等待回签、解释交易的时间;Nord 的 10 份咨询合同按 250 美元一份算是 2,500 美元。
  4. 把控制权变更暴露当损失。 75,744 美元是「对方有权重新选择」的收入,不是已经丢掉的收入;它影响价格与托管,不等于当期亏损。
  5. 资产收购不查供应商合同。 供应商金额小,但禁转条款和付款主体变更足以让服务在交割后中断。
  6. 劳动合同只换主体、不换角色。 合伙人从股东变成买方员工,涉及职责、汇报线和留任期;只改合同抬头,人还是会走。
  7. 用卖方时薪估买方成本。 85 美元是 Nord 的咨询费率,买方法务、采购和 PM 的时薪不同;同一份合同在两边的成本不一样。
  8. 交割后才发现合同转不过去。 转让同意要在交割前拿到,至少要在托管释放前拿到;交割后再谈,卖方已经失去议价位置。
  9. 把平台账号写进合同承继清单。 Steam 等平台账号不是合同,平台规则可能直接禁止转让;它属于账号与授权交割,不是合同转让。
  10. 只看前 3 家客户。 前 3 家占 48%,但剩下 52% 分布在 97 份合同里;大客户要一家一家谈,小客户要批量处理,两套流程都要有。

本节的边界是「合同可分类、单份工时可按组估算、控制权变更概率可假设」的静态模型。它不判断某一条款是否有效,也不覆盖跨境劳动法、集体协商、供应商独家条款和平台规则;这些要把具体合同交给律师审阅。合同承继完成后,下一节处理人的问题:竞业、保密与留任,见 9.2 竞业、保密与留任 ;客户迁移的操作层见 8.2 客户、合同与账号迁移 。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「books」更多文章

  1. 《移动游戏产品的架构·商业化与发行工程》附录 C 商业化与发行评审清单
  2. 《移动游戏产品的架构·商业化与发行工程》附录 B 双案例商业化事实表
  3. 《移动游戏产品的架构·商业化与发行工程》附录 A 商业化与发行术语速查