The remove_env_flags function was incorrectly returning index 0 when no --env flag was present, causing it to mistakenly skip the first argument (program name) and shift all arguments incorrectly. This required users to specify 'generate' twice to make the CLI work. Fix by checking if --env was actually found (env_idx > 0) before removing any arguments from the list.
rusty-petroleum
A petroleum transaction invoice generator with MariaDB backend.
Overview
This project processes petroleum/fuel station transaction data from CSV files and generates customer invoices. It stores transaction data in MariaDB for both invoicing and sales reporting.
Features
- CSV Import: Import transaction data from fuel station CSV files into MariaDB
- Invoice Generation: Generate HTML invoices from CSV data (file-to-file mode)
- Multi-Environment: Separate databases for development, testing, and production
- Sales Reporting: Query transactions by customer, product, date range
- Test-Driven Development: Comprehensive test suite with 45 tests
Project Structure
rusty-petroleum/
├── Cargo.toml # Rust dependencies
├── Cargo.lock # Locked dependency versions
├── config.example.toml # Config template
├── migrations/ # SQL schema files
│ └── 002_schema.sql # Current schema
├── input/ # CSV input files
├── output/ # Generated invoices
├── src/
│ ├── lib.rs # Library crate (for testing)
│ ├── main.rs # CLI entry point
│ ├── config.rs # Configuration loading
│ ├── db/ # Database layer
│ │ ├── mod.rs
│ │ ├── connection.rs
│ │ ├── models.rs
│ │ └── repository.rs
│ ├── commands/ # CLI commands
│ │ ├── mod.rs
│ │ ├── db.rs # db setup/reset
│ │ └── import.rs # CSV import
│ └── invoice_generator.rs
├── templates/ # HTML invoice templates
│ ├── index.html
│ └── customer.html
└── tests/ # Integration tests
├── common/ # Test utilities
│ ├── mod.rs
│ ├── fixtures.rs
│ └── test_db.rs
├── config_test.rs # Config module tests
├── import_test.rs # CSV parsing tests
├── models_test.rs # Model tests
└── repository_test.rs # Database tests
Database Schema
customers
| Column | Type | Description |
|---|---|---|
| id | INT UNSIGNED | Primary key |
| customer_number | VARCHAR | Unique customer identifier |
| card_report_group | TINYINT UNSIGNED | Customer classification (1=fleet, 3/4=retail) |
| created_at | TIMESTAMP | Record creation time |
| updated_at | TIMESTAMP | Last update time |
cards
| Column | Type | Description |
|---|---|---|
| id | INT UNSIGNED | Primary key |
| card_number | VARCHAR | Unique card identifier |
| customer_id | INT UNSIGNED | FK to customers |
| created_at | TIMESTAMP | Record creation time |
| updated_at | TIMESTAMP | Last update time |
transactions
| Column | Type | Description |
|---|---|---|
| id | BIGINT UNSIGNED | Primary key |
| transaction_date | DATETIME | Transaction timestamp |
| batch_number | VARCHAR | Batch identifier |
| amount | DECIMAL(10,2) | Transaction amount |
| volume | DECIMAL(10,3) | Volume in liters |
| price | DECIMAL(8,4) | Price per liter |
| quality_code | INT | Product code |
| quality_name | VARCHAR | Product name (95 Oktan, Diesel) |
| card_number | VARCHAR | Card used (including anonymized) |
| station | VARCHAR | Station ID |
| terminal | VARCHAR | Terminal ID |
| pump | VARCHAR | Pump number |
| receipt | VARCHAR | Receipt number |
| control_number | VARCHAR | Control/verification number |
| customer_id | INT UNSIGNED | FK to customers (NULL for anonymized) |
| created_at | TIMESTAMP | Record creation time |
Configuration
Copy the example config and edit with your database credentials:
cp config.example.toml config.dev.toml # or config.test.toml or config.prod.toml
Edit config.dev.toml:
[database]
host = "localhost"
port = 3306
user = "your_user"
password = "your_password"
name = "rusty_petroleum_dev"
Environment Config Loading
Config files are loaded in order:
config.toml(local override, gitignored)config.<env>.toml(environment-specific, gitignored)config.example.toml(fallback, tracked)
Database Names by Environment
rusty_petroleum_dev- Developmentrusty_petroleum_test- Testingrusty_petroleum_prod- Production
Commands
# Database management
cargo run -- db setup --env <dev|test|prod> # Create database and schema
cargo run -- db reset --env <dev|test|prod> # Drop and recreate database
# Import data
cargo run -- import <csv-file> --env <dev|test|prod> # Import to database (default: prod)
# Generate invoices (file-to-file, no database)
cargo run -- generate <csv-file> <output-dir>
Usage Examples
# Setup development database
cargo run -- db setup --env dev
# Import transactions to dev database
cargo run -- import input/409.csv --env dev
# Reset development database
cargo run -- db reset --env dev
# Generate HTML invoices from CSV
cargo run -- generate input/409.csv output/
Testing
The project has a comprehensive test suite with 45 tests covering config, CSV parsing, models, and database operations.
# Run all tests (lib + integration)
cargo test
# Run only lib/unit tests (fast, no database needed)
cargo test --lib
# Run only integration tests (requires test database)
cargo test --tests
# Run a specific test file
cargo test --test config_test
cargo test --test import_test
cargo test --test repository_test
# Run a specific test
cargo test customer_insert_returns_id
# Run tests in release mode
cargo test --release
Test Database Setup
Repository tests require a test database. Run setup before testing:
cargo run -- db setup --env test
Production Build
Build an optimized binary for production:
# Build release binary
cargo build --release
# Run the binary
./target/release/invoice-generator db setup --env prod
./target/release/invoice-generator import data.csv --env prod
Current Status
Implemented
- Database schema for transactions, customers, cards
- CSV import to MariaDB
- Multi-environment support (dev/test/prod)
- Configuration via TOML files
- Invoice generation (HTML output)
- Database setup/reset commands
- Unit tests (45 tests)
TODO
- Sales reporting queries (dashboard/API)
- Customer invoice retrieval from database
- Batch import across multiple CSV files
- CI/CD pipeline
Technology Stack
- Language: Rust (Edition 2021)
- Database: MariaDB
- ORM: sqlx (async MySQL)
- Templating: Askama (HTML templates)
- Config: TOML
- Testing: tokio-test, tempfile
Getting Started
-
Install Rust (if not already installed)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -
Create database user and grant permissions in MariaDB
CREATE USER 'your_user'@'%' IDENTIFIED BY 'your_password'; GRANT ALL PRIVILEGES ON rusty_petroleum_dev.* TO 'your_user'@'%'; CREATE DATABASE rusty_petroleum_dev; -
Setup configuration
cp config.example.toml config.dev.toml # Edit config.dev.toml with your credentials -
Setup database and import data
cargo run -- db setup --env dev cargo run -- import input/409.csv --env dev -
Run tests
cargo test --lib # Unit tests (fast) cargo test --tests # Integration tests (requires DB)
License
See LICENSE file.