ブライダル

婚礼・宴会の見積書の金額計算を、見積もりシステムに実装する。サービス料の掛け方、税率ごとに1回の端数処理、値引きの按分、2027年4月からの飲食料品の税率引下げ(法案の成立が前提)に備えて施行日で税率を決める作り

婚礼や宴会の見積書を作るシステムの金額計算を、依存の無いJavaScriptで書いてテストしました。サービス料、税率ごとの合計と1回の端数処理、値引きの按分、施行日から税率を引く表と、表に無い日付で止める作りまで。

この記事の結論:婚礼や宴会の見積書の金額は、「数量を決める → サービス料を足す → 税率ごとに合計する → 値引きを按分する → 税率ごとに1回だけ消費税の端数を処理する」の順で計算します。税率は品目に持たせた税区分と、譲渡の日(この記事では施行日)から、適用期間つきの表を引いて決め、表に無い日付では計算を止めます。2026年9月15日の閣議決定の大綱では、2027年4月1日から2029年3月31日まで飲食料品の税率を1%に下げる案が示されており(法案の成立が前提)、1年以上先の施行を扱う婚礼の見積もりは、この表の作りで備えられます。依存の無いJavaScriptで書き、2026年10月5日に手元のNode.jsで、施行日を変えた計算と7件のテストを流しました。

この記事の内容のご相談は株式会社bundlyzeへbundlyzeのWEB戦略チーム代行について見てみる

株式会社bundlyzeでは、業務で毎日使われるシステムを、要件の整理から作り始め、動き出した後の保守まで担っています。結婚式場やホテルの婚礼・宴会部門の見積もりの仕組みについては、品目と変更履歴の持ち方を「婚礼見積もりの「上がり幅」を減らす見積もりシステムの設計」に、請求書の記載事項と保存を「請求書の発行・受領を自動化する設計」に書きました。この記事はその間にある「見積書の金額をどう計算するか」だけを扱います。税の取り扱いは国税庁と財務省の公開資料から確かめた範囲で書き、個々の品目がどの税率になるかの判断は税理士に確かめる前提です。

見積もりの金額は、5つの段階の順番を決めてから計算する

見積書の合計がずれる原因の多くは、計算の順番が担当者や表計算のシートごとに違うことです。サービス料を値引きの前に掛けるか後に掛けるか、消費税を行ごとに出すか税率ごとに出すかで、同じ明細でも合計が数円から数百円ずれます。システムにするときは、次の順番を1つに決めて、すべての見積もりで同じ計算を通します。

順番 段階 この記事のコードでの決め方
1 数量 品目の単位(1名・1卓・1式)と、人数・1卓の人数から決める
2 サービス料 対象の印がある品目の合計に率を掛け、1円未満を端数処理する
3 税率ごとの合計 品目の税区分と譲渡の日から税率を引き、税率ごとに合計する
4 値引き 税率ごとの合計の比で按分し、按分の合計を値引き額と一致させる
5 消費税 値引き後の税率ごとの合計に税率を掛け、税率ごとに1回だけ端数処理する

サービス料を値引きの前に計算するか後に計算するかは、式場や宴会場の決まりによります。この記事では値引きの前に計算していますが、順番は設定として持ち、どちらで計算した見積書かを後から追えるようにしておくのが安全です。

税率は、品目の税区分と譲渡の日から表を引いて決める

税率は品目に直接書かず、「税区分」と「譲渡の日」の2つから表を引いて決めます。品目マスタに「10%」と書いてしまうと、税率が変わる日をまたぐ見積もりで、品目マスタを書き換えるしかなくなるからです。

婚礼や宴会の料理と飲み物は、多くの場合、標準税率の区分に入ります。国税庁の軽減税率制度のQ&A(個別事例編)の問72では、旅館やホテルの宴会場、会議室・研修室などで行われる飲食料品の提供は、ホテルなど自体やテナントのレストランが行うものであれば「食事の提供」に当たり、軽減税率の対象にならないとされています。一方で、持ち帰る菓子のように、品目によっては「飲食料品の譲渡」に当たるかを確かめる必要があるものもあります。どの品目をどの区分に置くかは、この記事では決めません。システムでは区分を品目マスタの項目として持ち、税理士と確かめた結果を入れる作りにします。

税率の表には、区分ごとに適用の開始日と終了日を持たせます。

税区分 適用の期間 税率 根拠
standard(標準) 2019-10-01〜 10% 現行
food(飲食料品の譲渡) 2019-10-01〜2027-03-31 8% 現行の軽減税率
food(飲食料品の譲渡) 2027-04-01〜2029-03-31 1% 2026年9月15日閣議決定の大綱。法案の成立が前提

2029年4月1日以後の飲食料品の行は、あえて入れていません。国税庁のQ&Aが説明しているのは2年間の引下げで、その後のことは書かれていないためです。表に無い日付が来たら、推測で税率を埋めずに計算を止め、表を直すまで見積書を出せないようにします。

2027年4月からの飲食料品の税率引下げ案で、見積もりシステムが確かめること

婚礼の見積もりは、施行の1年以上前に作ることも珍しくありません。2026年10月の今作っている見積もりのうち、施行日が2027年4月1日以後のものは、引下げの期間に入ります。国税庁の「飲食料品に係る2年間の消費税率引下げに関するQ&A」(2026年9月)から、見積もりの計算に関係するところを抜き出すと、次のとおりです。

  • 期間と税率。 2027年4月1日から2029年3月31日までの2年間、飲食料品の譲渡の税率が1%(国・地方の合計)になる(問1-1)
  • 対象の範囲。 今8%の対象になっている飲食料品の範囲と同じ。外食やケータリング等は対象外で10%のまま(問1-2)
  • いつの取引から。 2027年4月1日以後に行う飲食料品の譲渡から。3月31日までに結んだ契約に基づくものでも、4月1日以後に行うものは、経過的な取扱いに当たる場合を除いて1%(問1-3)
  • 経過的な取扱い。 一定の定期供給契約と通信販売に基づくものは8%のまま。選んで使うものではない(問1-6)
  • 前提。 Q&Aの内容は、法案が国会に提出され、審議を経て可決・成立した場合のもの

見積もりシステムでいえば、「契約した日」ではなく「譲渡の日」で税率を引く作りにしておけば、施行日が4月1日の前か後かで自動的に税率が分かれます。婚礼の契約や品目が経過的な取扱いに当たるかどうかは、この記事では判断していません。当たる品目があるなら、品目ごとに日付や区分を変えて持てるようにしておきます。

サービス料は、掛ける品目と税区分を設定として持つ

サービス料は、すべての品目に掛かるとは限りません。料理と飲み物と会場使用料には掛けて、装花や衣装、持ち込みの品目には掛けない、といった決まりは式場や宴会場ごとに違います。品目マスタに「サービス料の対象」の印を持たせ、対象の行の合計にだけ率を掛けます。

サービス料そのものにも、消費税がかかる前提で組みます。財務省の「総額表示に関する主な質問について」には、税抜3,000円の食事にサービス料10%の300円を足した3,300円に、消費税330円が加わる計算の例が載っています。この記事のコードでは、サービス料の行を標準の税区分に置いています。サービス料の率と端数処理の方法、区分は、設定として1か所に持ちます。

値引きは、税率ごとの金額の比で按分する

税率の違う品目が混ざった見積もりで、まとめて値引きをするときは、値引きをどの税率から引いたかを決めておかないと、税率ごとの消費税が計算できません。国税庁のQ&A(個別事例編)の問93は、まとめて値引きした場合の値引き後の金額は、値引き前の金額の比で按分するなど合理的に計算する、としています。あわせて、税率ごとの値引き額が領収書などの書類で確かめられれば、片方の税率の品目からだけ値引きしても、合理的に区分されているものに当たると説明しています。

この記事のコードは、値引き前の税率ごとの合計の比で按分しています。按分で1円未満が出たときは切り捨て、残りを最後の税率に寄せて、按分の合計がいつも値引き額と一致するようにしました。どちらの方法にするかは、見積書と請求書でそろえておきます。

消費税の端数処理は、税率ごとに1回だけにする

消費税の1円未満の端数は、税率ごとの合計に税率を掛けたあとで、1回だけ処理します。国税庁のインボイス制度のQ&A(問57)では、適格請求書に書く消費税額の端数処理は、1枚の適格請求書につき税率ごとに1回行う必要があり、商品ごとに端数を処理して合計することは認められない、とされています。切上げ・切捨て・四捨五入のどれにするかは任意です。

見積書そのものは、この決まりの対象ではありません。それでも見積もりの段階から同じ計算にしておくのは、見積もりの合計と最終の請求書の合計が、計算の仕方の違いだけでずれるのを防ぐためです。後のテストでは、105円の品目3つ(8%)で、行ごとに処理すると消費税が24円、税率ごとに1回で処理すると25円になることを確かめています。

実装したコード(estimate.mjs)

金額はすべて円の整数で持ち、税額などは整数に率を掛けてから100で割って、最後に端数処理しています。依存するライブラリはありません。

js
// 婚礼・宴会の見積もりの金額計算(依存なし)。金額はすべて円の整数で持つ

// 税区分ごとの税率と、適用する期間。表にない日付は計算を止める(推測で埋めない)
export const RATE_TABLE = [
  { cat: 'standard', from: '2019-10-01', to: null, rate: 10 },
  { cat: 'food', from: '2019-10-01', to: '2027-03-31', rate: 8 },
  // 2026-09-15 閣議決定の大綱による引下げ。法案が成立した場合の内容
  { cat: 'food', from: '2027-04-01', to: '2029-03-31', rate: 1 },
];

export function rateFor(cat, date, table = RATE_TABLE) {
  const hit = table.find((r) => r.cat === cat && r.from <= date && (r.to === null || date <= r.to));
  if (!hit) throw new Error(`税率が決まっていない: ${cat} ${date}`);
  return hit.rate;
}

const ROUND = { floor: Math.floor, ceil: Math.ceil, round: Math.round };

// 品目の数量を、人数・卓数・1式から決める
function quantity(item, est) {
  if (item.unit === 'guest') return est.guests;
  if (item.unit === 'table') return Math.ceil(est.guests / est.perTable);
  return item.qty ?? 1;
}

export function calculate(est, table = RATE_TABLE) {
  const round = ROUND[est.rounding];
  if (!round) throw new Error(`端数処理の方法が不明: ${est.rounding}`);

  // 1. 品目の行(税抜)。譲渡の日は品目ごとに持てる(無ければ施行日)
  const lines = est.items.map((it) => {
    const qty = quantity(it, est);
    const date = it.date ?? est.eventDate;
    return { ...it, qty, date, amount: it.unitPrice * qty, rate: rateFor(it.cat, date, table) };
  });

  // 2. サービス料:対象の行の合計に率を掛け、1円未満は端数処理。税区分は設定で決める
  const base = lines.filter((l) => l.service).reduce((s, l) => s + l.amount, 0);
  if (est.serviceRate > 0) {
    lines.push({ name: `サービス料(${est.serviceRate}%)`, cat: est.serviceCat, date: est.eventDate,
      amount: round((base * est.serviceRate) / 100), rate: rateFor(est.serviceCat, est.eventDate, table) });
  }

  // 3. 税率ごとに合計する
  const groups = new Map();
  for (const l of lines) groups.set(l.rate, (groups.get(l.rate) ?? 0) + l.amount);

  // 4. 値引き:税率ごとの合計の比で按分する。端数は最後の税率に寄せ、合計を必ず一致させる
  const rates = [...groups.keys()].sort((a, b) => b - a);
  const before = rates.reduce((s, r) => s + groups.get(r), 0);
  const discount = est.discount ?? 0;
  let left = discount;
  const byRate = rates.map((rate, i) => {
    const d = i === rates.length - 1 ? left : Math.floor((discount * groups.get(rate)) / before);
    left -= d;
    const net = groups.get(rate) - d;
    // 5. 消費税は税率ごとに1回だけ端数処理する
    return { rate, gross: groups.get(rate), discount: d, net, tax: round((net * rate) / 100) };
  });

  const net = byRate.reduce((s, g) => s + g.net, 0);
  const tax = byRate.reduce((s, g) => s + g.tax, 0);
  return { lines, byRate, net, tax, total: net + tax };
}

品目の date は、品目ごとに譲渡の日が施行日と違うときのための項目です。たとえば、施行日より前に渡す品目があるなら、その品目だけ日付を持たせます。

施行日を2027年4月1日の前と後にして、同じ見積もりを流した結果

同じ明細で、施行日だけを2027年3月27日と4月10日にして計算しました。単価は計算を確かめるための仮の値で、相場ではありません。持ち帰りの菓子は、計算の違いを見るために仮に「飲食料品の譲渡」の区分に置いています。

js
import { calculate } from './estimate.mjs';

// 単価は計算を確かめるための仮の値(相場ではない)
const items = [
  { name: '料理', unit: 'guest', unitPrice: 10000, cat: 'standard', service: true },
  { name: '飲み物', unit: 'guest', unitPrice: 3333, cat: 'standard', service: true },
  { name: '会場使用料', unit: 'set', unitPrice: 100000, cat: 'standard', service: true },
  { name: '装花(卓ごと)', unit: 'table', unitPrice: 8000, cat: 'standard', service: false },
  // 持ち帰りの菓子。ここでは仮に「飲食料品の譲渡」に置く(実際の区分は税理士と決める)
  { name: '持ち帰りの菓子', unit: 'guest', unitPrice: 1111, cat: 'food', service: false },
];
const base = { guests: 61, perTable: 8, items, serviceRate: 10, serviceCat: 'standard', rounding: 'floor', discount: 50000 };

for (const eventDate of ['2027-03-27', '2027-04-10']) {
  const r = calculate({ ...base, eventDate });
  console.log(`施行日 ${eventDate}`);
  for (const l of r.lines) console.log(`  ${l.name}:${l.amount} 円(税率 ${l.rate}%)`);
  for (const g of r.byRate) console.log(`  [${g.rate}%] 合計 ${g.gross} − 値引き ${g.discount} = ${g.net}  消費税 ${g.tax}`);
  console.log(`  税抜 ${r.net} + 消費税 ${r.tax} = 税込 ${r.total} 円\n`);
}
try { calculate({ ...base, eventDate: '2029-04-07' }); } catch (e) { console.log(`施行日 2029-04-07 → ${e.message}`); }

2026年10月5日に手元で流した出力です。

施行日 2027-03-27
  料理:610000 円(税率 10%)
  飲み物:203313 円(税率 10%)
  会場使用料:100000 円(税率 10%)
  装花(卓ごと):64000 円(税率 10%)
  持ち帰りの菓子:67771 円(税率 8%)
  サービス料(10%):91331 円(税率 10%)
  [10%] 合計 1068644 − 値引き 47018 = 1021626  消費税 102162
  [8%] 合計 67771 − 値引き 2982 = 64789  消費税 5183
  税抜 1086415 + 消費税 107345 = 税込 1193760 円

施行日 2027-04-10
  料理:610000 円(税率 10%)
  飲み物:203313 円(税率 10%)
  会場使用料:100000 円(税率 10%)
  装花(卓ごと):64000 円(税率 10%)
  持ち帰りの菓子:67771 円(税率 1%)
  サービス料(10%):91331 円(税率 10%)
  [10%] 合計 1068644 − 値引き 47018 = 1021626  消費税 102162
  [1%] 合計 67771 − 値引き 2982 = 64789  消費税 647
  税抜 1086415 + 消費税 102809 = 税込 1189224 円

施行日 2029-04-07 → 税率が決まっていない: food 2029-04-07

違いが出たのは、飲食料品の区分に置いた行の消費税だけでした(5,183円 → 647円)。料理・飲み物・サービス料などの標準の区分は、施行日が4月の前でも後でも10%のままで、値引きの按分も同じです。2029年4月7日を施行日にすると、表に飲食料品の税率が無いので、計算は止まりました。

テストで固定した7つのこと

税率の境目と、計算の順番の決まりをテストにしておくと、後から品目や設定を足したときに、計算が黙って変わるのを防げます。Node.jsに組み込みのテストランナー(node:test)で書きました。

js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { calculate, rateFor } from './estimate.mjs';

const est = (over = {}) => ({ eventDate: '2027-03-27', guests: 61, perTable: 8, serviceRate: 0,
  serviceCat: 'standard', rounding: 'floor', discount: 0, items: [], ...over });

test('税率は譲渡の日で決まる。2027-03-31 と 04-01 の境目', () => {
  assert.equal(rateFor('food', '2027-03-31'), 8);
  assert.equal(rateFor('food', '2027-04-01'), 1);
  assert.equal(rateFor('standard', '2027-04-01'), 10);
});

test('表にない期間は計算を止める', () => {
  assert.throws(() => rateFor('food', '2029-04-01'), /税率が決まっていない/);
});

test('品目ごとの日付が施行日より優先される', () => {
  const r = calculate(est({ eventDate: '2027-04-03',
    items: [{ name: '先に渡す菓子', unit: 'set', unitPrice: 1000, cat: 'food', date: '2027-03-31' }] }));
  assert.equal(r.lines[0].rate, 8);
});

test('卓数は人数を1卓の人数で割って切り上げる', () => {
  const r = calculate(est({ items: [{ name: '装花', unit: 'table', unitPrice: 1000, cat: 'standard' }] }));
  assert.equal(r.lines[0].qty, 8); // 61 ÷ 8 = 7.625 → 8卓
});

test('サービス料は対象の行だけに掛け、1円未満を切り捨てる', () => {
  const r = calculate(est({ serviceRate: 10, items: [
    { name: '料理', unit: 'set', unitPrice: 12345, cat: 'standard', service: true },
    { name: '装花', unit: 'set', unitPrice: 50000, cat: 'standard', service: false },
  ] }));
  assert.equal(r.lines.at(-1).amount, 1234); // 12345 × 10% = 1234.5 → 1234
});

test('消費税は税率ごとに1回だけ端数処理する(行ごとに処理した合計とは違う)', () => {
  const items = [1, 2, 3].map((i) => ({ name: `菓子${i}`, unit: 'set', unitPrice: 105, cat: 'food' }));
  const r = calculate(est({ items }));
  const perLine = items.reduce((s, it) => s + Math.floor((it.unitPrice * 8) / 100), 0);
  assert.equal(r.tax, 25);  // 315 × 8% = 25.2 → 25
  assert.equal(perLine, 24); // 8.4 → 8 を3回足すと 24
});

test('値引きは税率ごとに按分し、按分の合計は値引き額と一致する', () => {
  const r = calculate(est({ discount: 10001, items: [
    { name: '料理', unit: 'set', unitPrice: 70000, cat: 'standard' },
    { name: '菓子', unit: 'set', unitPrice: 30001, cat: 'food' },
  ] }));
  assert.equal(r.byRate.reduce((s, g) => s + g.discount, 0), 10001);
  assert.equal(r.net, 100001 - 10001);
});

node --test estimate.test.mjs の結果です。

ok 1 - 税率は譲渡の日で決まる。2027-03-31 と 04-01 の境目
ok 2 - 表にない期間は計算を止める
ok 3 - 品目ごとの日付が施行日より優先される
ok 4 - 卓数は人数を1卓の人数で割って切り上げる
ok 5 - サービス料は対象の行だけに掛け、1円未満を切り捨てる
ok 6 - 消費税は税率ごとに1回だけ端数処理する(行ごとに処理した合計とは違う)
ok 7 - 値引きは税率ごとに按分し、按分の合計は値引き額と一致する
# tests 7
# pass 7
# fail 0

見積書と、Webサイトのプランや見積り例とで違うこと

消費税の総額表示(税込価格の表示)の義務は、見積書には掛かりません。財務省の「総額表示に関する主な質問について」は、総額表示の義務は不特定かつ多数の人にあらかじめ価格を表示する場合が対象なので、見積書・契約書・請求書は対象にならない、としています。ただし、広告やホームページに「見積り例」を載せる場合は、不特定かつ多数の人にあらかじめ価格を表示する場合に当たると注意しています。

公式サイトのプランページや見積り例は、見積書と同じ計算から作ると、Webに出す金額と実際の見積書の金額の食い違いを減らせます。お二人が見るページでの金額の見せ方は「結婚式場の見積もり・プラン比較ページをWebで分かりやすくする設計」にまとめています。なお、国税庁のQ&Aには、引下げに伴う飲食料品の総額表示の特例(2027年2月1日から5月31日まで)も載っています。

作り始める前に、式場・宴会場の側で決めること

計算の部品は小さく作れますが、決まりが決まっていないと作れません。次の6つを、経理や税理士と一緒に決めてから実装に入ります。

  1. 品目ごとの税区分。 持ち帰る品目など、標準の区分ではない可能性がある品目を洗い出して確かめる
  2. 譲渡の日の決め方。 施行日で一律にするか、先に渡す品目に別の日を持たせるか
  3. サービス料の対象と率、計算の順番。 値引きの前か後か、サービス料の区分
  4. 値引きの按分の方法。 金額の比で按分するか、決まった税率から引いて書類に明記するか
  5. 端数処理の方法。 切捨て・切上げ・四捨五入のどれにするか(請求書とそろえる)
  6. 税率の表を誰がいつ直すか。 法案の成立や政令の公布を見て、表を更新する担当と手順

株式会社bundlyzeでは、こうした計算の決まりを要件として整理するところから、見積もり・予約・請求をつなぐシステムの設計と開発、動き出した後の保守までをお受けしています。進め方は業務システムの開発支援のページで確かめられます。

動作確認した環境

確認日は2026年10月5日です。

  • macOS 26.6.2、Node.js 22.23.2(node:test)
  • 記事のコード(estimate.mjs・demo.mjs・estimate.test.mjs)を手元で流した出力を、そのまま載せています

確かめていないこと:

  • 法案の国会での成立と、成立後の条文・政令の中身(この記事の表は2026年9月の国税庁のQ&Aに基づく)
  • 婚礼・宴会で扱う個々の品目(持ち帰りの菓子など)の税区分。記事では仮に置いただけです
  • 婚礼の契約や品目が、定期供給契約・通信販売の経過的な取扱いに当たるかどうか
  • サービス料の扱いのうち、財務省の例(飲食店のサービス料)以外の形のもの

参照した公式資料(確認日:2026年10月5日)

税率の引下げは法案の審議中で、Q&Aも随時改訂される予定と書かれています。実装の前と、公開した後も定期的に読み直してください。

よくある質問

見積書の消費税は、品目の行ごとに計算して足してもよいですか?

請求書を適格請求書(インボイス)として出すときは、国税庁のQ&Aで、消費税額の1円未満の端数処理は1枚につき税率ごとに1回とされ、品目ごとに端数を処理して合計することは認められていません。見積書そのものはこの決まりの対象ではありませんが、見積もりと最終の請求で計算の仕方が違うと金額がずれるので、見積もりの段階から税率ごとに1回で計算しておくのが安全です。

2027年4月から飲食料品の消費税率が1%になると聞きました。婚礼の料理も下がりますか?

国税庁のQ&A(2026年9月)では、引下げの対象は今8%の対象になっている飲食料品の譲渡と同じ範囲で、外食やケータリングは10%のままと説明されています。また同じ国税庁のQ&Aで、ホテルなどの宴会場で行う飲食料品の提供は「食事の提供」に当たり軽減税率の対象にならないとされています。なお、この引下げは法案が国会で成立した場合の内容です。

今年結んだ契約で、2027年4月以後に行う結婚式の税率はどうなりますか?

国税庁のQ&Aでは、2027年3月31日までに結んだ契約でも、4月1日以後に行う飲食料品の譲渡は、定期供給契約と通信販売の経過的な取扱いに当たる場合を除いて1%とされています。見積もりシステムは、契約日ではなく譲渡の日(この記事では施行日、品目ごとに別の日も持てる形)で税率を引く作りにしておきます。個々の品目がどの税率になるかは、税理士に確かめてください。

値引きは、どの税率の品目から引けばよいですか?

国税庁のQ&Aでは、税率の違う品目をまとめて値引きしたときは、値引き前の金額の比で按分するなど合理的に分けることとされ、税率ごとの値引き額が領収書などで確かめられれば、片方の税率の品目からだけ引いても合理的に区分されているものに当たると説明されています。この記事のコードは、値引き前の金額の比で按分しています。

この記事の書き方と確認の方針