Skip to content

Instantly share code, notes, and snippets.

@theSage21
Created October 25, 2021 16:40
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save theSage21/29261e071efd92f701b69ddd56374fa6 to your computer and use it in GitHub Desktop.
Save theSage21/29261e071efd92f701b69ddd56374fa6 to your computer and use it in GitHub Desktop.
import os
import zipfile
import json
from num2words import num2words
import pendulum
import subprocess
TEMPLATE_DOC = "InvoiceTemplate.docx"
DATE_FMT = "YYYY-MMM-DD"
close_tr = "</w:tr>"
open_tr = "<w:tr>"
br = '</w:t><w:br /><w:t xml:space="preserve">'
def as_money(num):
result = ""
for i, c in enumerate(reversed(list(str(num)))):
if i in [3, 5, 7, 9]:
result += ","
result += c
return "".join(reversed(list(result))) + "/-"
def get_row_template(doc):
row_start = -1
desc_start = doc.index("{{ ITEM_DESCRIPTION }}")
while True:
new_start = doc.index(open_tr, row_start + 1)
if new_start >= desc_start:
break
row_start = new_start
row_end = doc.index(close_tr, desc_start)
return doc[row_start : row_end + len(close_tr)]
def docx_replace(old_file, new_file, rep):
print(old_file, new_file)
zin = zipfile.ZipFile(old_file, "r")
zout = zipfile.ZipFile(new_file, "w")
for item in zin.infolist():
buffer = zin.read(item.filename)
if item.filename == "word/document.xml":
res = buffer.decode("utf-8")
for r in rep:
if r == "{{ ITEMS }}":
last_tr = None
row_template = None
for nth_item, bill_item in enumerate(rep[r]):
bill_item["{{ SR_NO }}"] = nth_item + 1
if nth_item == 0:
last_tr = res.index("{{ ITEM_DESCRIPTION }}")
row_template = get_row_template(res)
for k, v in bill_item.items():
res = res.replace(k, f"{v}")
last_tr = res.index(close_tr, last_tr)
last_tr += len(close_tr)
else:
nr = row_template.replace(
"{{ ITEM_SR }}", str(nth_item + 1)
)
for k, v in bill_item.items():
nr = nr.replace(k, f"{v}")
res = res[:last_tr] + nr + res[last_tr:]
last_tr += len(nr)
else:
if res != res.replace(r, rep[r]):
res = res.replace(r, rep[r])
buffer = res.encode("utf-8")
zout.writestr(item, buffer)
zout.close()
zin.close()
print("Wrote file")
def make_bill(invoice_number, bill_to, items, pay_to_details, gen_on):
assert invoice_number
fname = invoice_number.replace("/", "_")
if os.path.exists(f"bills/{fname}.pdf"):
print(f"Already generated: {fname}")
return
gen_on = pendulum.parse(gen_on)
pay_before = gen_on.add(days=7).format(DATE_FMT)
gen_on = gen_on.format(DATE_FMT)
config = {}
# -- calculate item totals
total = 0
for item in items:
if "item_qty" not in item:
item["item_rate"] = item["item_amt"]
item["item_qty"] = 1
else:
item["item_amt"] = item["item_rate"] * item["item_qty"]
total += item["item_amt"]
item["item_amt"] = as_money(item["item_amt"])
for key in list(item.keys()):
ckey = "{{ " + key.upper() + " }}"
item[ckey] = str(item.pop(key))
total_words = num2words(total, lang="en_IN") + " only"
total_words = total_words.capitalize()
total = as_money(total)
# -- set context for docx template filling
for key in locals():
if key in {
"items",
"gen_on",
"pay_before",
"bill_to",
"invoice_number",
"pay_to_details",
"total",
"total_words",
}:
ckey = "{{ " + key.upper() + " }}"
config[ckey] = locals()[key]
docx_replace(TEMPLATE_DOC, f"bills/{fname}.docx", config)
subprocess.run(
f"libreoffice --headless --convert-to pdf bills/{fname}.docx --outdir bills && rm bills/{fname}.docx",
shell=True,
)
if __name__ == "__main__":
from bills import bills
for bill in bills:
make_bill(**bill._asdict())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment