Invoice System

[[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