[[Back]]
Create new invoice based off of the invoice template
Generate html + pdf invoice with pandoc the transmit invoice to customer.
Once cleared payment update watermark re generate invoices as above and transmit to customer as receipt.
#Produces HTML Invoice pandoc invoice.md -o invoice.html #Produces PDF Invoice pandoc invoice.md --css=print.css -o invoice.pdf --pdf-engine=weasyprint
Copy Template to New Invoice file using file naming convention of company. eg. YYYYMMDD-Customer-INV#.md
scriptable eg
#!/bin/bash # NewInvoice # Programatically produce date in format YYYYMMDD # Read In Customer Name # Read In INV# (maybe programatically calculate) echo "Please enter new Invoice Filename: [YYYYMMDD-Customer-INV#.md] " read name cp Invoice/Templeate.md Invoice/Current/$name
script print Invoice to file
#!/bin/bash # use fzf to locate invoice to print # Produce PDF of Invoice # LPR print PDF file created # Delete PDF
Email Invoice
#!/bin/bash # use fzf to locate invoice to email out. # produce PDF of invoice # move to public accessible folder on webserver # generate a link to invoice # read in email address echo "Please enter email to send invoice to: " read email mailto: $email, mail template.html
better still email with python
import email, smtplib, ssl
from email import encoders
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
subject = "An email with attachment from Python"
body = "This is an email with attachment sent from Python"
sender_email = "my@gmail.com"
receiver_email = input("Enter Email to deliver invoice to: [your@gmail.com]")
password = input("Type your password and press enter:")
# Create a multipart message and set headers
message = MIMEMultipart()
message["From"] = sender_email
message["To"] = receiver_email
message["Subject"] = subject
message["Bcc"] = receiver_email # Recommended for mass emails
# Add body to email
message.attach(MIMEText(body, "plain"))
filename = "document.pdf" # In same directory as script - > Use FZF below to return a valid file.
# Open PDF file in binary mode
with open(filename, "rb") as attachment:
# Add file as application/octet-stream
# Email client can usually download this automatically as attachment
part = MIMEBase("application", "octet-stream")
part.set_payload(attachment.read())
# Encode file in ASCII characters to send by email
encoders.encode_base64(part)
# Add header as key/value pair to attachment part
part.add_header(
"Content-Disposition",
f"attachment; filename= {filename}",
)
# Add attachment to message and convert message to string
message.attach(part)
text = message.as_string()
# Log in to server using secure context and send email
context = ssl.create_default_context()
with smtplib.SMTP_SSL("smtp.gmail.com", 465, context=context) as server:
server.login(sender_email, password)
server.sendmail(sender_email, receiver_email, text)
import subprocess
import sys
def find_file_with_fzf(start_directory="."):
"""
Uses fzf to interactively select a file within a given directory.
Args:
start_directory (str): The directory to start the search from.
Defaults to the current directory.
Returns:
str or None: The path of the selected file, or None if no file was selected.
"""
try:
# Use 'find' to generate a list of files and pipe it to fzf
# -L follows symbolic links
# -type f finds only files
# -print0 ensures null-terminated output for safety with filenames containing spaces
find_command = ["find", start_directory, "-L", "-type", "f", "-print0"]
fzf_command = ["fzf", "--read0", "--print0"] # --read0 and --print0 for null-terminated input/output
# Execute find and pipe its output to fzf
find_process = subprocess.Popen(find_command, stdout=subprocess.PIPE)
fzf_process = subprocess.run(
fzf_command,
stdin=find_process.stdout,
capture_output=True,
encoding="utf-8",
check=False # Do not raise an exception for non-zero exit codes (e.g., user cancels fzf)
)
find_process.stdout.close() # Close the pipe to find_process
if fzf_process.returncode == 0:
selected_file = fzf_process.stdout.strip().replace('\x00', '') # Remove null characters
return selected_file if selected_file else None
else:
# fzf returns non-zero if the user cancels (e.g., Ctrl+C)
print("fzf was cancelled or encountered an error.", file=sys.stderr)
return None
except FileNotFoundError:
print("Error: 'fzf' or 'find' command not found. Please ensure they are installed and in your PATH.", file=sys.stderr)
return None
except Exception as e:
print(f"An unexpected error occurred: {e}", file=sys.stderr)
return None