This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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