BOI: machinery import duty and import VAT, PVC duty relief
Deploy test site / deploy (push) Successful in 35s

User, October 2026: take the BOI duty-free import and VAT exemption into
consideration.

- boi.md: import VAT goes with the BOI duty exemption (Revenue Department
  answer on the BOI OSOS FAQ, checked October 2026); what it is worth:
  ~2.75M USD of imported machinery at the start fleet (block midpoints)
  would otherwise carry ~5 % duty (Thai MFN averages 4.1 % on HS 8477,
  0 to 10 % across HS 84; estimate until the HS codes are set), ~0.14M
  USD, and 7 % import VAT, ~0.2M paid at the border and recovered later
  as input tax, mostly as a refund claim since exports are zero-rated.
- total-investment.md: the blocks are vendor prices without duty or
  import VAT, so the headline already assumes promotion.
- Fleet calculator: BOI promoted / not promoted switch and export share.
  Not promoted adds the machinery duty to capex and depreciation, shows the
  import VAT as cash at the border, and charges duty on imported PVC
  compound; promoted exempts both, with no compound duty on exported
  records and 90 % off for domestic sales (sections 36(1) and 30). A note
  under the KPIs shows what promotion saves either way. Defaults: payback
  3.1 years with BOI, 3.2 without; adverse case operating result -31k
  with BOI, -68k without. CIT and BOI CIT exemptions still not modelled.
This commit is contained in:
grabowski committed 2026-10-10 20:29:32 +07:00
1 parent 1a368287f8
commit 8032e0ea47
4 files changed
+68 -11

No files matched your search

+2 -1
View File
@@ -187,7 +187,8 @@ The detail lives in dedicated documents:
electricity and steam, labour from base salaries with social security,
on-costs and health care for all staff, overheads, EBITDA, depreciation
and payback, with the revenue model's adverse, central and favourable
cases and USD, THB or EUR; every assumption is editable, with its
cases, BOI promotion on or off (machinery import duty and import VAT,
PVC duty relief) and USD, THB or EUR; every assumption is editable, with its
source, and a scenario can be shared by link
- [Test site](site/README.md): `site/` builds https://cnxvinyl.lol/, the
internal test site with demo pages from `docs/` (the plant operation
+16 -1
View File
@@ -39,7 +39,22 @@ not on "we press plastic discs."
## What Group B still delivers (the core value)
- **Machinery import duty exemption**: significant with the entire equipment
list imported (Germany, Sweden, USA, Japan)
list imported (Germany, Sweden, USA, Japan). **Import VAT goes with it:**
machinery or raw material imported with the duty exemption is exempt
from VAT at import as well (Revenue Department answer on the
[BOI OSOS FAQ](https://osos.boi.go.th/One-Stop/faq-group/205/FAQ-about-VAT--BOI),
checked October 2026); otherwise a BOI company is treated like any other
for VAT. What it is worth: the capex blocks in
[total investment](finance/total-investment.md) are vendor prices
without duty or VAT, so they already assume promotion. Without it, the
~2.75M USD of imported machinery at the start fleet (presses, galvanics,
Sibert, lathe system, metrology; block midpoints) would carry import duty,
roughly 5 % (Thailand's MFN averages 4.1 % on HS 8477 plastics
machinery, 0 to 10 % across HS 84; estimate until the HS codes are set),
~0.14M USD, and 7 % import VAT, ~0.2M USD paid at the border and only
recovered later as input tax, mostly as a refund claim because exports
are zero-rated. The [press fleet calculator](fleet-calculator.html)
shows both cases.
- **100% foreign ownership** of the promoted manufacturing company,
overriding Foreign Business Act restrictions for the promoted activity
- **Land ownership** for the promoted operation despite foreign
+4 -1
View File
@@ -75,7 +75,10 @@ exactly this reason.
- **BOI**: machinery duty exemption applies across most of the equipment
blocks, and the solar and automation spend converts into CIT-exemption
headroom; the application precedes every purchase order (see
[boi.md](../boi.md))
[boi.md](../boi.md)). The blocks are vendor prices without Thai duty or
import VAT, so the headline assumes promotion: without it, ~0.14M USD of
duty and ~0.2M USD of import VAT (reclaimable later) come on top at the
start fleet (estimate)
- **Buy vs lease** on land and building swings roughly **1.0M to 2.1M
USD (~34M to 69M THB)** of upfront capital, and also moves the launch
date by about three quarters — see
+46 -8
View File
@@ -236,6 +236,12 @@ document.documentElement.setAttribute("data-theme",t);})();
<button data-v="EUR">EUR</button>
</div>
<p class="hint" id="priceHint"></p>
<div class="row" style="margin-top:12px"><span>BOI promotion</span></div>
<div class="seg" id="boi">
<button data-v="on">Promoted</button>
<button data-v="off">Not promoted</button>
</div>
<label class="row"><span>Records exported, %<br><small>exports: no duty on their raw material (BOI), VAT zero-rated</small></span><input type="number" id="exportShare" min="0" max="100" step="5"></label>
</section>
<section class="card">
@@ -264,6 +270,7 @@ document.documentElement.setAttribute("data-theme",t);})();
<section class="card">
<h2>Money a year</h2>
<div class="kpis" id="ecoK"></div>
<div id="boiNote"></div>
<div class="bar" id="ecoBar" style="height:28px"></div>
<div class="legend" id="ecoLeg"></div>
<div class="tscroll" style="margin-top:12px"><table id="ecoT"></table></div>
@@ -334,6 +341,14 @@ document.documentElement.setAttribute("data-theme",t);})();
the plant. Lacquer blanks (70 EUR each) and the cutting engineer (50,000 THB a month) are team
figures; shipping cartons have no figure on file and are an estimate. Missing as the revenue model
lists: indirect labour, freight, inventory, effluent, commission, styli, extra galvanics units' capex.</p>
<p><b>BOI.</b> The capex blocks in total-investment.md are vendor prices without Thai import duty or
VAT, so they already assume BOI promotion: machinery imported under it pays no duty (Investment
Promotion Act section 28) and, with the duty, no import VAT (Revenue Department, BOI OSOS FAQ).
"Not promoted" adds the duty on the imported machinery to the capex and shows the import VAT as cash
paid at the border, reclaimable as input tax. Raw material: no duty on compound for exported
records, 90 % off for domestic sales. Revenue is net of VAT (exports zero-rated, 7 % on domestic
sales passed on). Corporate income tax and BOI's CIT exemptions (solar, automation, R&D) are not
modelled.</p>
<p><b>Labour.</b> Base salaries per role (revenue-model.md, its loaded rates divided by 1.3; the cutting
engineer a team figure), plus the employer's 5 % social security up to the 17,500 THB ceiling, 25 %
for overtime, bonus and PPE, and the company's health care for every member of staff, office
@@ -355,7 +370,7 @@ document.documentElement.setAttribute("data-theme",t);})();
const S0 = {
mode: "target", target: 1000000, spec: 20, grow: "classic", dMin: 2, cMin: 1,
dFleet: 2, cFleet: 1, shifts: 2, hrs: 8, days: 300, eff: 65, rateD: 3, rateC: 2, specF: 100,
priceStd: 3.8, premSpec: 1.15, titleFee: 1155, cur: "USD",
priceStd: 3.8, premSpec: 1.15, titleFee: 1155, cur: "USD", boi: "on", exportShare: 70,
};
// [group, id, label, value, unit, kind, source]
const AS = [];
@@ -433,6 +448,14 @@ grp("Money", [
["capexC", "Capex per Classic beyond one", 375000, "USD", "doc", "total-investment.md: ~290 to 460k, no Newbilt price on file"],
["deprYears", "Depreciation period", 12.5, "years", "doc", "revenue-model.md: 10 to 15 years"],
]);
grp("BOI and imports", [
["impMach", "Imported machinery in the start-fleet capex", 2750000, "USD", "doc", "total-investment.md block midpoints at vendor prices: presses 1.0M, galvanics 485k, Sibert 250k, lathe system 435k, metrology 580k; added presses count as imported"],
["dutyMach", "Import duty on that machinery without BOI", 5, "%", "est", "Estimate: Thai MFN averages 4.1 % on HS 8477 plastics machinery (customs-compliance.ai, March 2026), 0 to 10 % across HS 84; set per line once the HS codes are known"],
["vatRate", "VAT", 7, "%", "doc", "Thai VAT; with BOI duty exemption the import VAT is exempt too (Revenue Department, BOI OSOS FAQ; boi.md)"],
["pvcImp", "Share of PVC compound imported", 100, "%", "est", "Estimate: record-grade compound from the specialist suppliers"],
["dutyPVC", "Import duty on PVC compound without BOI", 5, "%", "est", "Estimate; no Thai tariff line checked for record compound yet"],
["boiRawRed", "BOI duty reduction on raw material for domestic sales", 90, "%", "doc", "boi.md: exempt for export production (section 36(1)), up to 90 % off for domestic sales (section 30)"],
]);
grp("Materials and packaging", [
["pvcPrice", "PVC compound", 2.3, "USD/kg", "doc", "revenue-model.md: 1.75 to 2.90 USD/kg record grade"],
["labelCost", "Labels, a pair", 0.06, "USD", "doc", "revenue-model.md: 0.03 to 0.09"],
@@ -597,13 +620,19 @@ function headsOf(r) {
};
}
// Money a year in USD. Lines: [group, label, USD, basis].
function econ(r, b) {
function econ(r, b, boiOn = S.boi !== "off") {
const fx = A.thbUsd > 0 ? A.thbUsd : 33, t2u = v => v / fx;
const X = r.X, pressed = X * (1 + A.reject / 100), st = X - r.sp, L = [];
const add = (g, l, v, s) => L.push([g, l, v, s]);
const revStd = st * S.priceStd, revSpec = r.sp * (S.priceStd + S.premSpec), revTitle = r.titles * S.titleFee;
const rev = revStd + revSpec + revTitle;
add("mat", "PVC compound", r.compound * 1000 * A.pvcPrice, `${f0(r.compound)} t at ${f2(A.pvcPrice)} USD/kg incl. rejects; trim back as regrind`);
const expSh = Math.min(100, Math.max(0, S.exportShare)) / 100;
const pvcImported = r.compound * 1000 * A.pvcPrice * A.pvcImp / 100;
const pvcDuty = pvcImported * A.dutyPVC / 100 * (boiOn ? (1 - expSh) * (1 - A.boiRawRed / 100) : 1);
add("mat", "Import duty on PVC compound", pvcDuty, boiOn
? `BOI: none on the ${pct(expSh)} exported, ${f0(A.boiRawRed)} % off the rest`
: `${f1(A.dutyPVC)} % on ${pct(A.pvcImp / 100)} imported compound, no BOI`);
add("mat", "Labels", pressed * A.labelCost, "a printed pair per pressed record");
add("pack", "Inner sleeves", X * A.sleeveCost, "");
add("pack", "Jackets (printed cartons)", X * A.jacketCost, "local carton partners");
@@ -638,9 +667,12 @@ function econ(r, b) {
`yearly for everyone, plus risk-factor tests for ${ppl(h.exposed)} on the press floor, in galvanics and maintenance`);
const sum = gs => L.filter(l => gs.includes(l[0])).reduce((a, l) => a + l[2], 0);
const direct = L.reduce((a, l) => a + l[2], 0), contrib = rev - direct, ebitda = contrib - A.overheads;
const capex = A.capexBase + (r.D - 2) * A.capexD + (r.C - 1) * A.capexC, depr = capex / Math.max(1, A.deprYears);
const fleetDelta = (r.D - 2) * A.capexD + (r.C - 1) * A.capexC;
const imp = Math.max(0, A.impMach + fleetDelta);
const dutyM = boiOn ? 0 : imp * A.dutyMach / 100, vatM = boiOn ? 0 : (imp + dutyM) * A.vatRate / 100;
const capex = A.capexBase + fleetDelta + dutyM, depr = capex / Math.max(1, A.deprYears);
return { rev, revStd, revSpec, revTitle, L, sum, direct, contrib, ebitda, capex, depr, op: ebitda - depr,
payback: ebitda > 0 ? capex / ebitda : Infinity, heads: h };
payback: ebitda > 0 ? capex / ebitda : Infinity, heads: h, imp, dutyM, vatM, pvcDuty, boiOn };
}
function scenario() {
const b = basis(S);
@@ -680,7 +712,7 @@ function render() {
$("specV").textContent = S.spec + " %";
$("targetBox").hidden = S.mode !== "target";
$("fleetBox").hidden = S.mode !== "fleet";
for (const g of ["mode", "grow", "cur"]) for (const bt of $(g).children) bt.classList.toggle("on", bt.dataset.v === S[g]);
for (const g of ["mode", "grow", "cur", "boi"]) for (const bt of $(g).children) bt.classList.toggle("on", bt.dataset.v === S[g]);
const cs = caseNow();
for (const bt of $("case").children) bt.classList.toggle("on", bt.dataset.v === cs);
@@ -738,6 +770,11 @@ function render() {
kpi(money(e.op), "operating profit after depreciation") +
kpi(perRec(e.direct), "direct cost per record") +
kpi(isFinite(e.payback) ? f1(e.payback) + " years" : "never", `payback on ${money(e.capex)} capex`);
const eOn = econ(r, b, true), eOff = econ(r, b, false);
const pb = x => isFinite(x.payback) ? f1(x.payback) + " years" : "none";
$("boiNote").innerHTML = e.boiOn
? `<div class="flag ok">BOI promotion: no import duty (~${money(eOff.dutyM)}) and no import VAT (~${money(eOff.vatM)}) on ${money(eOn.imp)} of imported machinery, and ${money(eOff.pvcDuty - eOn.pvcDuty)} a year less duty on PVC compound. Without promotion the capex would be ${money(eOff.capex)} and the payback ${pb(eOff)}.</div>`
: `<div class="flag">Not promoted: ${money(eOff.dutyM)} import duty on ${money(eOff.imp)} of imported machinery goes into the capex and its depreciation; ${money(eOff.vatM)} import VAT is paid at the border and only comes back later as input tax (exports are zero-rated, so mostly as a refund claim); ${money(eOff.pvcDuty)} a year duty on PVC compound. With BOI the payback would be ${pb(eOn)}.</div>`;
const SEG = [["PVC and labels", e.sum(["mat"]), "#b07d48"], ["Packaging", e.sum(["pack"]), "#c9a24a"],
["Lacquers and plating", e.sum(["metal"]), "#8e98a6"], ["Energy", e.sum(["energy"]), "#e2462a"],
["Labour", e.sum(["lab"]), "#2f7de1"], ["Maintenance", e.sum(["maint"]), "#7a6fd6"],
@@ -764,7 +801,7 @@ function render() {
PR("Contribution", e.contrib, e.rev > 0 ? pct(e.contrib / e.rev) + " of revenue" : "", true);
PR("Overheads", A.overheads, "admin, sales, insurance, facility, IT, fees, marketing");
PR("EBITDA", e.ebitda, e.rev > 0 ? pct(e.ebitda / e.rev) + " of revenue" : "", true);
PR("Depreciation", e.depr, `${money(e.capex)} capex over ${f1(A.deprYears)} years`);
PR("Depreciation", e.depr, `${money(e.capex)} capex${e.dutyM > 0 ? ` incl. ${money(e.dutyM)} import duty` : ""} over ${f1(A.deprYears)} years`);
PR("Operating profit", e.op, isFinite(e.payback) ? `payback ${f1(e.payback)} years on EBITDA` : "no payback", true);
$("ecoT").innerHTML = `<thead><tr><th>Line</th><th class="n">A year</th><th class="n">A record</th><th>Basis</th></tr></thead><tbody>${P.join("")}</tbody>`;
@@ -976,7 +1013,7 @@ function drawPark(r, b) {
}
// ---------------------------------------------------------------- inputs and state
const NUM = ["target", "dMin", "cMin", "dFleet", "cFleet", "shifts", "hrs", "days", "eff", "rateD", "rateC", "specF", "priceStd", "premSpec", "titleFee"];
const NUM = ["target", "dMin", "cMin", "dFleet", "cFleet", "shifts", "hrs", "days", "eff", "rateD", "rateC", "specF", "priceStd", "premSpec", "titleFee", "exportShare"];
function syncInputs() {
for (const k of NUM) $(k).value = S[k];
$("targetR").value = S.target;
@@ -1009,6 +1046,7 @@ function readHash() {
if (!["target", "fleet"].includes(S.mode)) S.mode = "target";
if (!["classic", "duplex"].includes(S.grow)) S.grow = "classic";
if (!["USD", "THB", "EUR"].includes(S.cur)) S.cur = "USD";
if (!["on", "off"].includes(S.boi)) S.boi = "on";
}
function num(el, fallback) { const v = parseFloat(el.value); return isFinite(v) ? v : fallback; }
@@ -1022,7 +1060,7 @@ $("case").addEventListener("click", e => {
const v = e.target.dataset && e.target.dataset.v; if (!v || !CASES[v]) return;
Object.assign(S, CASES[v].S); Object.assign(A, CASES[v].A); syncInputs(); render();
});
for (const g of ["mode", "grow", "cur"]) $(g).addEventListener("click", e => {
for (const g of ["mode", "grow", "cur", "boi"]) $(g).addEventListener("click", e => {
const v = e.target.dataset && e.target.dataset.v; if (!v) return;
if (g === "mode" && v === "fleet" && S.mode === "target") { const { r } = scenario(); S.dFleet = r.D; S.cFleet = r.C; syncInputs(); }
S[g] = v; render();