Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
from typing import List, Sequence
from .dates_days_between import days_between ← from dates.days-between ^1.0.0 · built alongside by fune
from .finance_tax_vat_return_types import VatReturn, VatTransaction
from .math_round_div import round_div ← from math.round-div ^1.0.0 · built alongside by fune
from .money_add import add_money ← from money.add ^1.0.0 · built alongside by fune
from .money_amount import Money, money ← from money.amount ^1.0.0 · built alongside by fune
_ZERO_VAT_SALES = ("exempt", "outside-scope", "domestic-reverse-charge", "services-reverse-charge", "ni-eu-goods")
_ZERO_VAT_PURCHASES = ("exempt", "outside-scope")
_TREATMENTS = (
"taxable",
"exempt",
"outside-scope",
"domestic-reverse-charge",
"services-reverse-charge",
"postponed-import-vat",
"ni-eu-goods",
)
def _whole_pounds(amount: Money) -> Money:
"""Drop the pence, towards zero: boxes 6 to 9 are whole pounds."""
return money(round_div(amount.minor, 100, "down") * 100, amount.currency)
def vat_return(transactions: Sequence[VatTransaction], period_start: str, period_end: str) -> VatReturn:
"""The nine boxes of a UK VAT return for a period.
Each transaction feeds the boxes its direction and treatment call for
(VAT Notice 700/12 sections 3 and 4.6); boxes 6 to 9 drop the pence after
totalling.
"""
if days_between(period_start, period_end) < 0:
raise ValueError("VAT return period ends %s, before it starts %s" % (period_end, period_start))
box: List[Money] = [money(0, "GBP") for _ in range(10)]
count = 0
for index, t in enumerate(transactions):
n = index + 1
if t.direction not in ("sale", "purchase"):
raise ValueError('transaction %d: unknown direction "%s"' % (n, t.direction))
if t.treatment not in _TREATMENTS:
raise ValueError('transaction %d: unknown VAT treatment "%s"' % (n, t.treatment))
if t.net.currency != "GBP" or t.vat.currency != "GBP":
raise ValueError("transaction %d: amounts must be in GBP" % (n,))
sale = t.direction == "sale"
if sale and t.treatment == "postponed-import-vat":
raise ValueError("transaction %d: postponed-import-vat applies to purchases only" % (n,))
if t.vat.minor != 0 and t.treatment in (_ZERO_VAT_SALES if sale else _ZERO_VAT_PURCHASES):
raise ValueError("transaction %d: %s %ss must have zero VAT" % (n, t.treatment, t.direction))
if days_between(period_start, t.date) < 0 or days_between(t.date, period_end) < 0:
continue
count += 1
def add(b: int, amount: Money) -> None:
box[b] = add_money(box[b], amount)
if sale:
if t.treatment == "taxable":
add(1, t.vat)
if t.treatment != "outside-scope":
add(6, t.net)
if t.treatment == "ni-eu-goods":
add(8, t.net)
elif t.treatment == "taxable":
add(4, t.vat)
add(7, t.net)
elif t.treatment == "exempt":
add(7, t.net)
elif t.treatment in ("domestic-reverse-charge", "services-reverse-charge", "postponed-import-vat"):
if t.treatment == "services-reverse-charge":
add(6, t.net)
add(1, t.vat)
add(4, t.vat)
add(7, t.net)
elif t.treatment == "ni-eu-goods":
add(2, t.vat)
add(4, t.vat)
add(7, t.net)
add(9, t.net)
box3 = add_money(box[1], box[2])
difference = box3.minor - box[4].minor
return VatReturn(
box1=box[1],
box2=box[2],
box3=box3,
box4=box[4],
box5=money(abs(difference), "GBP"),
box6=_whole_pounds(box[6]),
box7=_whole_pounds(box[7]),
box8=_whole_pounds(box[8]),
box9=_whole_pounds(box[9]),
repayment=difference < 0,
transactions=count,
)