Current Version: 0.9.0-beta.1
- 0.9.0-beta.1: Initial beta release with core invoice generation functionality
This project follows Semantic Versioning. For the versions available, see the tags on this repository.
Pre-Release Notice: This project is currently under development and not yet fully functional. This is a pre-release version with ongoing development and improvements.
A professional invoice generator for small businesses and freelancers, built with Flask and modern web technologies. This app allows you to manage company and client details, add labor and itemized costs, and generate professional invoices.
- User Guide - For end users: Learn how to use the application, manage clients, create invoices, and more.
- Developer Documentation - For developers: Technical documentation, API reference, and setup instructions (this README).
invoice_gen/
├── app.py # Main Flask application
├── requirements.txt # Python dependencies
├── templates/ # Jinja2 HTML templates
│ └── index.html # Main dashboard and invoice creation UI
├── static/ # Static files (CSS, JS, images)
│ └── logos/ # Uploaded company logos
├── venv/ # Python virtual environment (not tracked in git)
└── ... # Other supporting files
The application supports both development and production environments. The environment can be toggled using environment variables in the docker-compose.yml file.
To switch between development and production modes, you can use either of these methods:
-
Using the provided script (recommended):
# For development mode ./switch-env.sh dev # For production mode ./switch-env.sh prod
-
Using docker-compose directly:
# For development mode DOCKERFILE=Dockerfile.dev FLASK_ENV=development docker-compose up --build # For production mode docker-compose up --build
The script method is recommended as it handles stopping the current environment and provides clear feedback about the switching process.
- Uses Flask's development server with hot-reloading
- Debug mode enabled
- Mounts local directories for live code changes
- Uses HTTP instead of HTTPS
- More verbose logging
- Development-specific settings:
SESSION_COOKIE_SECURE = FalsePREFERRED_URL_SCHEME = 'http'- Debug PIN enabled for debugging
- Hot-reloading enabled
- Uses Gunicorn as WSGI server
- Debug mode disabled
- Optimized for performance
- Uses HTTPS (when configured)
- Minimal logging
- Production-specific settings:
SESSION_COOKIE_SECURE = TruePREFERRED_URL_SCHEME = 'https'- Debug PIN disabled
- Hot-reloading disabled
Key environment variables that control the environment:
FLASK_ENV: Set todevelopmentorproductionDOCKERFILE: Set toDockerfile.devfor development orDockerfilefor productionSECRET_KEY: Different keys for development and productionSCRIPT_NAME: Application root path (e.g.,/invoice)
-
Stop the current environment:
docker-compose down
-
Clear any cached data (optional):
docker-compose down -v
-
Start the desired environment:
# For development DOCKERFILE=Dockerfile.dev FLASK_ENV=development docker-compose up --build # For production docker-compose up --build
- Always use different secret keys for development and production
- Development mode should never be used in production
- Keep development-specific settings in
Dockerfile.dev - Production settings should be secure by default
- Database migrations work in both environments
- Static files are served differently in each environment
To enable address autocomplete using Google Places in the business details form, you must obtain a Google Maps API key with the Places API enabled. Follow these steps:
-
Create a Google Cloud Project
- Go to the Google Cloud Console.
- Sign in with your Google account.
- Click the project drop-down at the top and select New Project.
- Enter a project name (e.g., "InvoiceGen Address Picker") and click Create.
-
Enable Billing
- Go to the Billing page and link your project to a billing account.
- Google provides a generous free tier for Maps/Places usage.
-
Enable the Places API
- In the Cloud Console, make sure your project is selected.
- Go to the Places API page and click Enable.
- (Optional but recommended) Also enable the Maps JavaScript API.
-
Create an API Key
- Go to the Credentials page.
- Click + Create Credentials > API key.
- Copy the generated API key.
-
Restrict Your API Key (Recommended)
- On the Credentials page, click your new API key.
- Under API restrictions, select Restrict key and choose Places API and Maps JavaScript API.
- Under Application restrictions, select HTTP referrers and add your domain (e.g.,
localhostfor local development, or your production domain).
-
Configure Your Application
- Copy
example_credentials.initocredentials.iniin your project root. - Paste your API key as the value for
GOOGLE_MAPS_API_KEYincredentials.ini. - Do not commit
credentials.inito version control! (It is already in.gitignore.)
- Copy
-
Further Reading
- See the Google Maps Platform documentation for more details.
Note: The address picker will not function until a valid API key is provided.
The application provides several RESTful API endpoints for managing invoices, clients, and settings. All endpoints require authentication unless specified otherwise.
-
POST /register- Register a new user
- Required fields: username, password, email
- Returns: Redirect to login page on success
-
POST /login- Authenticate user
- Required fields: username/email, password
- Returns: Redirect to dashboard on success
-
GET /logout- Log out current user
- Returns: Redirect to login page
-
POST /new_client- Create a new client
- Required fields: name, address, email, phone
- Returns: Redirect to dashboard with success message
-
GET /get_client/<client_id>- Get client details
- Returns: JSON object with client information
{ "name": "string", "address": "string", "email": "string", "phone": "string" }
-
GET /get_company/<company_id>- Get company details
- Returns: JSON object with company information
{ "name": "string", "address": "string", "email": "string", "phone": "string", "logo_path": "string" } -
POST /update_company- Update company details
- Fields: company_name, company_address, company_email, hourly_rate, logo (optional)
- Returns: Redirect to dashboard with success message
-
GET /check_invoice_number/<invoice_number>- Check if invoice number exists
- Returns: JSON object with existence status
{ "exists": boolean } -
GET /download/<invoice_number>- Download invoice as Excel file
- Returns: Excel file download
-
GET /api/sales-tax- Get all sales tax rates
- Returns: Array of tax rate objects
[ { "id": number, "rate": number, "description": "string" } ] -
POST /api/sales-tax- Create new sales tax rate
- Required fields: rate, description
- Returns: Created tax rate object
{ "id": number, "rate": number, "description": "string" } -
DELETE /api/sales-tax- Delete all sales tax rates
- Returns: Success message
-
PUT /api/invoice/<invoice_id>/sales-tax- Update invoice sales tax settings
- Required fields: sales_tax_id, tax_applies_to
- Returns: Updated invoice tax information
{ "id": number, "sales_tax_id": number, "tax_applies_to": "string", "tax_rate": number, "tax_description": "string" }
POST /save_selections- Save selected business and client IDs
- Fields: businessId, clientId
- Returns: Success status
{ "success": boolean }
All API endpoints may return the following error responses:
400 Bad Request: Missing or invalid required fields401 Unauthorized: Authentication required404 Not Found: Requested resource not found500 Internal Server Error: Server-side error
The application includes a test harness for populating the database with test data. This is useful for manual testing and development purposes.
The test harness is located in the tests directory and can be run using the provided script:
# Run the test harness
./tests/run_test_harness.shThe test harness will:
- Clear any existing test data
- Create a test user with the following credentials:
- Username:
testuser - Password:
TestPass123!@# - Email:
test@example.com
- Username:
- Generate test data including:
- 3 test businesses with generated logos
- 5 test clients
- 10 test items
- 5 test labor items
- 5 tax rates (0%, 5%, 10%, 15%, 20%)
- 12 test invoices with various line items and labor entries
- Generate a summary of the created data in
test_data_summary.json
After running the test harness, you can log in to the application using the test user credentials to explore and test the functionality with the generated data.
The test harness uses the application's database and automatically cleans up any existing test data before creating new test data. This ensures a clean state for testing while preserving any production data.
The Invoice Generator uses a single HTML (Jinja2) template for invoice generation. You can customize this template to match your branding, layout, and required fields.
- Location:
invoice-gen/templates/invoice_pretty.html - Format: Standard HTML with Jinja2 templating syntax.
- Variables: You can use variables such as
{{ business_name }},{{ client_name }},{{ invoice_number }},{{ line_items }}, etc. (see the template for all available variables). - How to Customize:
- Edit the HTML and CSS directly in the template file.
- Use Jinja2 control structures (e.g.,
{% for item in line_items %}) to loop through items. - Add or remove fields as needed.
- Preview changes by generating a new invoice and viewing it in the app.
business_name,business_address,business_email,business_phoneclient_name,client_address,client_email,client_phoneinvoice_number,invoice_date,notes,subtotal,sales_tax,grand_totalline_items(list of items, each withdate,description,quantity,unit_price,total)
- Always back up your template before making major changes.
- After editing the template, generate a test invoice to ensure your changes render as expected.
- For advanced logic, use Jinja2 syntax in the template.
- Flask: Web framework for Python
- Jinja2: Templating engine for HTML
- WeasyPrint: For PDF generation (if used)
- Bootstrap (optional): For UI styling (if included in static)
- JavaScript: For dynamic UI updates (inline in templates)
-
Clone the repository:
git clone https://github.com/151henry151/invoice-gen.git cd invoice-gen -
Set up the virtual environment:
python -m venv venv source venv/bin/activate # On Windows: venv\Scripts\activate pip install -r requirements.txt
-
Database Migrations
The project uses Alembic for database migrations. Here's how to work with migrations:
a. Initial Setup
# The migrations directory is already set up in the project # To create a new migration after model changes: flask db migrate -m "Description of changes" # To apply pending migrations: flask db upgrade
b. Common Migration Commands
# View migration history flask db history # Downgrade to a specific version flask db downgrade <revision_id> # Upgrade to the latest version flask db upgrade
c. Migration Best Practices
- Always create a new migration when making model changes
- Test migrations both up and down before committing
- Include meaningful descriptions in migration messages
- Review generated migration files before applying them
- Back up your database before running migrations in production
d. Troubleshooting
- If migrations fail, check the error message and the migration file
- Use
flask db currentto see the current migration version - Use
flask db headsto see the latest migration version - If needed, you can manually edit migration files before applying them
-
Run the development server:
flask run
The app will be available at
http://127.0.0.1:5000/.
The project includes a Docker-based development environment that provides hot-reloading, debugging tools, and a consistent development experience.
-
Build and start the development containers:
docker-compose -f docker-compose.dev.yml up --build
-
Access the application:
- Main application: http://localhost:8080/invoice
- Debug toolbar will be available in the browser
- Hot Reloading: Changes to Python files automatically trigger a server reload
- Debug Mode: Detailed error messages and Flask debug toolbar
- Development Tools: Testing, formatting, and linting tools included
- Separate Database: Development database is isolated from production
- No Rebuilding: Code changes don't require container rebuilds
-
View logs:
docker-compose -f docker-compose.dev.yml logs -f
-
Run tests:
docker-compose -f docker-compose.dev.yml exec web pytest -
Format code:
docker-compose -f docker-compose.dev.yml exec web black .
-
Lint code:
docker-compose -f docker-compose.dev.yml exec web flake8 -
Stop the development environment:
docker-compose -f docker-compose.dev.yml down
- pytest: Testing framework
- pytest-cov: Test coverage reporting
- black: Code formatter
- flake8: Code linter
- flask-debugtoolbar: Debug toolbar for Flask applications
invoice-gen/
├── docker-compose.dev.yml # Development Docker Compose configuration
├── Dockerfile.dev # Development Dockerfile
├── entrypoint.dev.sh # Development container entrypoint script
└── ... # Other project files
The development environment mounts your local code directory into the container, allowing for immediate code changes without rebuilding. The database and migrations are stored in Docker volumes to persist data between container restarts.
This application can be built and run using Docker and Docker Compose, simplifying deployment and ensuring consistency across environments.
Prerequisites:
- Docker installed on your system.
- Docker Compose installed on your system.
Steps:
-
Clone the repository:
git clone https://github.com/151henry151/invoice-gen.git cd invoice-genNote: The
user_logosdirectory is included in the repository and will be automatically created when you clone it. This directory is used to store uploaded company logos. -
Build and run the application using Docker Compose:
docker-compose up --build
This command will build the Docker image (the first time it's run, or if the Dockerfile/code changes) and then start the application container. The
--buildflag ensures the image is rebuilt if necessary. -
Access the application: Once the container is running, the application will be accessible at http://localhost:8080/.
-
To stop the application: Press
Ctrl+Cin the terminal wheredocker-compose upis running. To remove the containers, you can run:docker-compose down
Alternative: Building and Running with Docker (without Docker Compose)
If you prefer to use Docker directly without Docker Compose:
-
Build the Docker image:
docker build -t invoice-gen-app . -
Run the Docker container:
docker run -p 8080:8080 -v "$(pwd)/user_logos:/app/static/logos" --name invoice-gen-container invoice-gen-appThis command runs the container, maps port 8080, mounts the
user_logosdirectory, and names the containerinvoice-gen-container. -
Access the application: http://localhost:8080/
-
To stop and remove the container:
docker stop invoice-gen-container docker rm invoice-gen-container
This application is designed to be deployed behind a reverse proxy (like Nginx) and can be run using Docker Compose. Here's a complete guide for production deployment:
-
Clone the repository:
git clone https://github.com/151henry151/invoice-gen.git cd invoice-genNote: The
user_logosdirectory is included in the repository and will be automatically created when you clone it. This directory is used to store uploaded company logos. -
Configure environment variables: Create a
.envfile in the project root with the following variables:FLASK_APP=wsgi.py FLASK_ENV=production SECRET_KEY=your-secure-secret-key-here DATABASE_URL=sqlite:///invoice_gen.db -
Build and start the Docker container:
docker-compose up -d --build
-
Configure Nginx:
- Copy the example configuration:
cp nginx.conf.example /etc/nginx/sites-available/invoice-gen
- Edit the configuration:
- Replace
your-domain.comwith your actual domain - Update SSL certificate paths
- Adjust the static files path to match your deployment
- Replace
- Enable the site:
ln -s /etc/nginx/sites-available/invoice-gen /etc/nginx/sites-enabled/
- Test and reload Nginx:
nginx -t systemctl reload nginx
- Copy the example configuration:
-
Set up SSL certificates:
certbot --nginx -d your-domain.com
-
Verify the deployment:
- Check container health:
docker ps docker logs invoice-gen-web-1
- Test the application:
- Visit
https://your-domain.com/invoice/ - Verify SSL certificate
- Check static file serving
- Test PDF generation
- Visit
- Check container health:
-
Database Persistence:
- The SQLite database is mounted as a volume in
docker-compose.yml - Regular backups are recommended
- The SQLite database is mounted as a volume in
-
Static Files:
- Company logos are stored in the
user_logosdirectory - Static files are served directly by Nginx for better performance
- Company logos are stored in the
-
Security:
- SSL/TLS is required for production
- Security headers are configured in Nginx
- Use strong secret keys in production
-
Monitoring:
- The container includes a healthcheck
- Monitor logs for errors:
docker logs -f invoice-gen-web-1
-
Updates:
- Pull latest changes:
git pull docker-compose up -d --build
- Database migrations may be required for schema updates
- Pull latest changes:
-
502 Bad Gateway:
- Check if the Docker container is running
- Verify Nginx proxy configuration
- Check container logs for errors
-
Static Files Not Loading:
- Verify Nginx static file configuration
- Check file permissions
- Ensure correct paths in configuration
-
Database Issues:
- Check database file permissions
- Verify volume mounting
- Check application logs for SQL errors
- Company and client management
- Add labor and itemized costs
- Live calculation of total costs
- Professional invoice generation
- Responsive, user-friendly UI
- All configuration is handled in
app.pyandtemplates/index.html. - Static assets (CSS, JS, images) are in the
static/directory. - For PDF export, ensure WeasyPrint is installed and configured if used.
This project uses Flask-Migrate (Alembic) to manage database migrations. This allows you to evolve your database schema over time without losing data.
If you need to reset the migration environment (e.g., during development or if the migration state is corrupted), follow these steps:
-
Backup and Reset Migrations:
- Inside the Docker container, run:
mv /app/migrations /app/migrations_backup_$(date +%Y%m%d_%H%M%S) flask db init - This creates a fresh
migrations/directory with a newalembic.ini.
- Inside the Docker container, run:
-
Configure the Database URL:
- Ensure that the
sqlalchemy.urlin/app/migrations/alembic.inipoints to your database. For example:[alembic] sqlalchemy.url = sqlite:////app/db/invoice_gen.db
- Ensure that the
-
Generate and Apply the Initial Migration:
- Run:
flask db migrate -m "Initial migration" flask db upgrade
- Run:
When you need to update your database schema (e.g., adding a new table or column):
- Update your SQLAlchemy models in
models.py. - Generate a new migration:
flask db migrate -m "Describe your change" - Apply the migration:
flask db upgrade
This workflow ensures that your database schema evolves safely in production without data loss.
This application is configured to run under the /invoice URL prefix (e.g., https://example.com/invoice/). This configuration is handled through the WSGI server (Gunicorn) using the SCRIPT_NAME environment variable, which is the recommended way to handle URL prefixes in Flask applications.
-
The
SCRIPT_NAMEenvironment variable is set indocker-compose.yml:environment: - SCRIPT_NAME=/invoice
-
Nginx is configured to pass this prefix to Gunicorn in
nginx/conf.d/hromp.com.conf:location /invoice/ { proxy_pass http://172.20.0.2:8080; # No trailing slash to preserve prefix proxy_set_header SCRIPT_NAME /invoice; # ... other proxy settings ... }
To serve the application under a different URL prefix or without a prefix:
-
Update the
SCRIPT_NAMEenvironment variable indocker-compose.yml:- For a different prefix (e.g.,
/billing):SCRIPT_NAME=/billing - For no prefix: Remove the
SCRIPT_NAMEline entirely
- For a different prefix (e.g.,
-
Update the Nginx configuration in
nginx/conf.d/hromp.com.conf:- For a different prefix:
location /billing/ { proxy_pass http://172.20.0.2:8080; proxy_set_header SCRIPT_NAME /billing; # ... other proxy settings ... }
- For no prefix:
location / { proxy_pass http://172.20.0.2:8080; # Remove the SCRIPT_NAME header # ... other proxy settings ... }
- For a different prefix:
-
Restart both services:
systemctl restart nginx docker-compose down && docker-compose up -d
- The Flask application itself doesn't need to know about the URL prefix - it's handled entirely by the WSGI server and reverse proxy
- All URLs in templates should use
url_for()to generate links, which will automatically include the correct prefix - Static files are automatically handled through the
SCRIPT_NAMEconfiguration - The application will work correctly whether served from the root path or any prefix
For questions or contributions, please open an issue or pull request on GitHub.