Initial commit with version management
This commit is contained in:
commit
375774594a
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
description:
|
||||||
|
globs:
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
# Docker Organization
|
||||||
|
|
||||||
|
- Keep all source code adjacent to its corresponding Dockerfile
|
||||||
|
- Avoid separating application code from its Docker configuration
|
||||||
|
- Place related files in the same directory as the Dockerfile they support
|
|
@ -0,0 +1,11 @@
|
||||||
|
---
|
||||||
|
description:
|
||||||
|
globs:
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
# File Management
|
||||||
|
|
||||||
|
- Avoid creating new files whenever possible
|
||||||
|
- Modify existing files in place rather than creating copies
|
||||||
|
- Use git for version control instead of creating backup files
|
||||||
|
- Only create new files when absolutely necessary for new functionality
|
|
@ -0,0 +1,106 @@
|
||||||
|
# Project Ploughshares
|
||||||
|
|
||||||
|
Transaction Management System for Project Ploughshares.
|
||||||
|
|
||||||
|
## Version
|
||||||
|
|
||||||
|
The current version is stored in the `VERSION` file. Use the `versionbump.sh` script to update the version number.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# To bump the patch version (e.g., 1.0.0 -> 1.0.1)
|
||||||
|
./versionbump.sh patch
|
||||||
|
|
||||||
|
# To bump the minor version (e.g., 1.0.0 -> 1.1.0)
|
||||||
|
./versionbump.sh minor
|
||||||
|
|
||||||
|
# To bump the major version (e.g., 1.0.0 -> 2.0.0)
|
||||||
|
./versionbump.sh major
|
||||||
|
```
|
||||||
|
|
||||||
|
## Docker Setup
|
||||||
|
|
||||||
|
The application is containerized using Docker and can be run using docker-compose.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build the containers
|
||||||
|
docker-compose build
|
||||||
|
|
||||||
|
# Start the application
|
||||||
|
docker-compose up
|
||||||
|
```
|
||||||
|
|
||||||
|
The application will be available at http://localhost:5001.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Transaction management (create, view, edit)
|
||||||
|
- Document uploads and attachments
|
||||||
|
- API endpoints for programmatic access
|
||||||
|
- PostgreSQL database for data storage
|
||||||
|
|
||||||
|
## Running the Application
|
||||||
|
|
||||||
|
### Using Docker (Recommended)
|
||||||
|
|
||||||
|
The application can be run using Docker:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run with PostgreSQL database
|
||||||
|
docker-compose up --build
|
||||||
|
```
|
||||||
|
|
||||||
|
This will:
|
||||||
|
1. Build the Docker image
|
||||||
|
2. Start PostgreSQL database
|
||||||
|
3. Initialize the database schema
|
||||||
|
4. Start the application on port 5001
|
||||||
|
|
||||||
|
#### Stopping the Application
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Stop all containers
|
||||||
|
docker-compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running Locally
|
||||||
|
|
||||||
|
1. Start PostgreSQL:
|
||||||
|
```bash
|
||||||
|
./start_postgres.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Initialize the database:
|
||||||
|
```bash
|
||||||
|
python init_db.py
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Start the application:
|
||||||
|
```bash
|
||||||
|
python app.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Documentation
|
||||||
|
|
||||||
|
API documentation is available at:
|
||||||
|
- http://localhost:5001/api-docs
|
||||||
|
- http://localhost:5001/api/docs
|
||||||
|
- http://localhost:5001/docs
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
To generate test data:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python generate_test_data.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Accessing the Application
|
||||||
|
|
||||||
|
The application runs on all addresses (0.0.0.0) and is accessible via:
|
||||||
|
- http://localhost:5001 (Docker)
|
||||||
|
- http://localhost:5001 (Local)
|
||||||
|
- http://<machine-ip>:5001 (Network access)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is licensed under the MIT License - see the LICENSE file for details.
|
|
@ -0,0 +1,44 @@
|
||||||
|
networks:
|
||||||
|
ploughshares-network:
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
build:
|
||||||
|
context: ./docker/ploughshares
|
||||||
|
ports:
|
||||||
|
- "5001:5001"
|
||||||
|
environment:
|
||||||
|
- FLASK_RUN_PORT=5001
|
||||||
|
- POSTGRES_HOST=db
|
||||||
|
- POSTGRES_PORT=5432
|
||||||
|
- POSTGRES_DB=ploughshares
|
||||||
|
- POSTGRES_USER=ploughshares
|
||||||
|
- POSTGRES_PASSWORD=ploughshares_password
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- ploughshares-network
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: postgres:12
|
||||||
|
environment:
|
||||||
|
- POSTGRES_DB=ploughshares
|
||||||
|
- POSTGRES_USER=ploughshares
|
||||||
|
- POSTGRES_PASSWORD=ploughshares_password
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
- ./docker/ploughshares/schema.sql:/docker-entrypoint-initdb.d/schema.sql
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U ploughshares -d ploughshares"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
networks:
|
||||||
|
- ploughshares-network
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres_data:
|
|
@ -0,0 +1,30 @@
|
||||||
|
FROM python:3.9-bullseye
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install system dependencies
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
libpq-dev \
|
||||||
|
gcc \
|
||||||
|
postgresql-client \
|
||||||
|
&& apt-get clean \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Copy requirements and install Python dependencies
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# Copy application code
|
||||||
|
COPY app.py .
|
||||||
|
COPY generate_test_data.py .
|
||||||
|
COPY schema.sql .
|
||||||
|
COPY templates/ ./templates/
|
||||||
|
|
||||||
|
# Create uploads directory
|
||||||
|
RUN mkdir -p uploads
|
||||||
|
|
||||||
|
# Expose the port the app runs on
|
||||||
|
EXPOSE 5001
|
||||||
|
|
||||||
|
# Command to run the application
|
||||||
|
CMD ["python", "app.py"]
|
|
@ -0,0 +1,233 @@
|
||||||
|
import os
|
||||||
|
import psycopg2
|
||||||
|
from psycopg2.extras import RealDictCursor
|
||||||
|
from flask import Flask, render_template, request, redirect, url_for, flash, jsonify, send_from_directory, abort
|
||||||
|
from werkzeug.utils import secure_filename
|
||||||
|
from datetime import datetime
|
||||||
|
import generate_test_data
|
||||||
|
import locale
|
||||||
|
|
||||||
|
# Get version from VERSION file
|
||||||
|
with open(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'VERSION'), 'r') as f:
|
||||||
|
VERSION = f.read().strip()
|
||||||
|
|
||||||
|
# Initialize the Flask app
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.secret_key = 'supersecretkey'
|
||||||
|
app.config['UPLOAD_FOLDER'] = 'uploads'
|
||||||
|
app.config['VERSION'] = VERSION
|
||||||
|
|
||||||
|
# Set locale for currency formatting
|
||||||
|
try:
|
||||||
|
locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
|
||||||
|
except locale.Error:
|
||||||
|
try:
|
||||||
|
locale.setlocale(locale.LC_ALL, '') # Use system default locale
|
||||||
|
except locale.Error:
|
||||||
|
pass # If all fails, we'll use the fallback in the filter
|
||||||
|
|
||||||
|
# Custom filter for currency formatting
|
||||||
|
@app.template_filter('currency')
|
||||||
|
def currency_filter(value):
|
||||||
|
if value is None:
|
||||||
|
return "$0.00"
|
||||||
|
try:
|
||||||
|
return locale.currency(float(value), grouping=True)
|
||||||
|
except (ValueError, TypeError, locale.Error):
|
||||||
|
# Fallback formatting if locale doesn't work
|
||||||
|
return f"${float(value):,.2f}"
|
||||||
|
|
||||||
|
# --- Database Connection ---
|
||||||
|
def get_db_connection():
|
||||||
|
host = os.environ.get('POSTGRES_HOST', 'db')
|
||||||
|
port = os.environ.get('POSTGRES_PORT', '5432')
|
||||||
|
dbname = os.environ.get('POSTGRES_DB', 'ploughshares')
|
||||||
|
user = os.environ.get('POSTGRES_USER', 'ploughshares')
|
||||||
|
password = os.environ.get('POSTGRES_PASSWORD', 'ploughshares_password')
|
||||||
|
|
||||||
|
try:
|
||||||
|
conn = psycopg2.connect(
|
||||||
|
host=host,
|
||||||
|
port=port,
|
||||||
|
dbname=dbname,
|
||||||
|
user=user,
|
||||||
|
password=password,
|
||||||
|
cursor_factory=RealDictCursor
|
||||||
|
)
|
||||||
|
conn.autocommit = True
|
||||||
|
print(f"Connected to PostgreSQL at {host}:{port}")
|
||||||
|
return conn
|
||||||
|
except psycopg2.OperationalError as e:
|
||||||
|
print(f"Error connecting to PostgreSQL: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# --- Routes ---
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
conn = get_db_connection()
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute('SELECT * FROM transactions ORDER BY id DESC')
|
||||||
|
transactions = cur.fetchall()
|
||||||
|
conn.close()
|
||||||
|
return render_template('index.html', transactions=transactions, version=VERSION)
|
||||||
|
|
||||||
|
@app.route('/api-docs')
|
||||||
|
def api_docs():
|
||||||
|
server_name = request.host
|
||||||
|
return render_template('api_docs.html', server_name=server_name, version=VERSION)
|
||||||
|
|
||||||
|
@app.route('/transaction/<int:id>')
|
||||||
|
def view_transaction(id):
|
||||||
|
conn = get_db_connection()
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute('SELECT * FROM transactions WHERE id = %s', (id,))
|
||||||
|
transaction = cur.fetchone()
|
||||||
|
conn.close()
|
||||||
|
if transaction is None:
|
||||||
|
abort(404)
|
||||||
|
return render_template('view_transaction.html', transaction=transaction, version=VERSION)
|
||||||
|
|
||||||
|
@app.route('/transaction/add', methods=['GET', 'POST'])
|
||||||
|
def create_transaction():
|
||||||
|
if request.method == 'POST':
|
||||||
|
# Get form data and set defaults for missing fields
|
||||||
|
data = request.form.to_dict()
|
||||||
|
default_fields = {
|
||||||
|
'transaction_type': '', 'company_division': '', 'address_1': '', 'address_2': '',
|
||||||
|
'city': '', 'province': '', 'region': '', 'postal_code': '', 'source_date': None,
|
||||||
|
'source_description': '', 'grant_type': '', 'description': '', 'amount': 0,
|
||||||
|
'recipient': '', 'commodity_class': '', 'contract_number': '', 'comments': ''
|
||||||
|
}
|
||||||
|
|
||||||
|
# Fill in missing fields with defaults
|
||||||
|
for field, default in default_fields.items():
|
||||||
|
if field not in data:
|
||||||
|
data[field] = default
|
||||||
|
|
||||||
|
# Handle checkbox fields
|
||||||
|
data['is_primary'] = 'is_primary' in data
|
||||||
|
|
||||||
|
conn = get_db_connection()
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO transactions (
|
||||||
|
transaction_type, company_division, address_1, address_2,
|
||||||
|
city, province, region, postal_code, is_primary, source_date, source_description,
|
||||||
|
grant_type, description, amount, recipient, commodity_class, contract_number, comments
|
||||||
|
) VALUES (
|
||||||
|
%(transaction_type)s, %(company_division)s, %(address_1)s, %(address_2)s,
|
||||||
|
%(city)s, %(province)s, %(region)s, %(postal_code)s, %(is_primary)s, %(source_date)s,
|
||||||
|
%(source_description)s, %(grant_type)s, %(description)s, %(amount)s, %(recipient)s,
|
||||||
|
%(commodity_class)s, %(contract_number)s, %(comments)s
|
||||||
|
) RETURNING id
|
||||||
|
""",
|
||||||
|
data
|
||||||
|
)
|
||||||
|
new_transaction_id = cur.fetchone()['id']
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
return redirect(url_for('view_transaction', id=new_transaction_id))
|
||||||
|
|
||||||
|
return render_template('transaction_form.html', version=VERSION)
|
||||||
|
|
||||||
|
@app.route('/transaction/<int:id>/edit', methods=['GET', 'POST'])
|
||||||
|
def update_transaction(id):
|
||||||
|
conn = get_db_connection()
|
||||||
|
if request.method == 'POST':
|
||||||
|
# Get form data and set defaults for missing fields
|
||||||
|
data = request.form.to_dict()
|
||||||
|
default_fields = {
|
||||||
|
'transaction_type': '', 'company_division': '', 'address_1': '', 'address_2': '',
|
||||||
|
'city': '', 'province': '', 'region': '', 'postal_code': '', 'source_date': None,
|
||||||
|
'source_description': '', 'grant_type': '', 'description': '', 'amount': 0,
|
||||||
|
'recipient': '', 'commodity_class': '', 'contract_number': '', 'comments': ''
|
||||||
|
}
|
||||||
|
|
||||||
|
# Fill in missing fields with defaults
|
||||||
|
for field, default in default_fields.items():
|
||||||
|
if field not in data:
|
||||||
|
data[field] = default
|
||||||
|
|
||||||
|
# Handle checkbox fields
|
||||||
|
data['is_primary'] = 'is_primary' in data
|
||||||
|
data['id'] = id
|
||||||
|
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
UPDATE transactions
|
||||||
|
SET transaction_type = %(transaction_type)s,
|
||||||
|
company_division = %(company_division)s,
|
||||||
|
address_1 = %(address_1)s,
|
||||||
|
address_2 = %(address_2)s,
|
||||||
|
city = %(city)s,
|
||||||
|
province = %(province)s,
|
||||||
|
region = %(region)s,
|
||||||
|
postal_code = %(postal_code)s,
|
||||||
|
is_primary = %(is_primary)s,
|
||||||
|
source_date = %(source_date)s,
|
||||||
|
source_description = %(source_description)s,
|
||||||
|
grant_type = %(grant_type)s,
|
||||||
|
description = %(description)s,
|
||||||
|
amount = %(amount)s,
|
||||||
|
recipient = %(recipient)s,
|
||||||
|
commodity_class = %(commodity_class)s,
|
||||||
|
contract_number = %(contract_number)s,
|
||||||
|
comments = %(comments)s
|
||||||
|
WHERE id = %(id)s
|
||||||
|
""",
|
||||||
|
data
|
||||||
|
)
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
return redirect(url_for('view_transaction', id=id))
|
||||||
|
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute('SELECT * FROM transactions WHERE id = %s', (id,))
|
||||||
|
transaction = cur.fetchone()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
if transaction is None:
|
||||||
|
abort(404)
|
||||||
|
return render_template('transaction_form.html', transaction=transaction, version=VERSION)
|
||||||
|
|
||||||
|
def bootstrap_database():
|
||||||
|
"""
|
||||||
|
Checks if the database is empty and populates it with test data if it is.
|
||||||
|
"""
|
||||||
|
print("Checking database for existing data...")
|
||||||
|
conn = get_db_connection()
|
||||||
|
if conn is None:
|
||||||
|
print("Database connection failed. Exiting.")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute("SELECT COUNT(*) FROM transactions")
|
||||||
|
count = cur.fetchone()['count']
|
||||||
|
if count == 0:
|
||||||
|
print("Database is empty. Populating with test data...")
|
||||||
|
test_data = generate_test_data.get_test_transactions()
|
||||||
|
for transaction in test_data:
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO transactions (
|
||||||
|
transaction_type, company_division, address_1, address_2,
|
||||||
|
city, province, region, postal_code, is_primary, source_date, source_description,
|
||||||
|
grant_type, description, amount, recipient, commodity_class, contract_number, comments
|
||||||
|
) VALUES (
|
||||||
|
%(transaction_type)s, %(company_division)s, %(address_1)s, %(address_2)s,
|
||||||
|
%(city)s, %(province)s, %(region)s, %(postal_code)s, %(is_primary)s, %(source_date)s,
|
||||||
|
%(source_description)s, %(grant_type)s, %(description)s, %(amount)s, %(recipient)s,
|
||||||
|
%(commodity_class)s, %(contract_number)s, %(comments)s
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
transaction
|
||||||
|
)
|
||||||
|
conn.commit()
|
||||||
|
print("Successfully inserted 10 test transactions.")
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
port = int(os.environ.get('FLASK_RUN_PORT', 5001))
|
||||||
|
app.run(host='0.0.0.0', port=port)
|
|
@ -0,0 +1,373 @@
|
||||||
|
{
|
||||||
|
"transactions": [
|
||||||
|
{
|
||||||
|
"transaction_id": 1,
|
||||||
|
"transaction_no": "78708",
|
||||||
|
"transaction_type": "Subcontract",
|
||||||
|
"company_division": "C A E Inc",
|
||||||
|
"address_1": "5585 Cote de Liesse",
|
||||||
|
"address_2": "P O Box 1800",
|
||||||
|
"city": "ST LAURENT",
|
||||||
|
"province": "QC",
|
||||||
|
"region": "Quebec",
|
||||||
|
"postal_code": "H4T 1G6",
|
||||||
|
"is_primary": true,
|
||||||
|
"source_date": "2023-08-23",
|
||||||
|
"source_description": "Source Description",
|
||||||
|
"grant_type": "Grant Type",
|
||||||
|
"description": "7000XR Full Flight Simulator (FFS) in Global 6000/6500 configuration (subc)",
|
||||||
|
"amount": 0.0,
|
||||||
|
"recipient": "US Army",
|
||||||
|
"commodity_class": "Aerospace",
|
||||||
|
"contract_number": "SUMMARY",
|
||||||
|
"comments": "Subcontract with Leidos, US, through CAE Defense & Security. In support of the High Accuracy Detection and Exploitation System (HADES) program.",
|
||||||
|
"created_at": "2025-07-02T19:30:43.640251"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"transaction_id": 2,
|
||||||
|
"transaction_no": "2021-11783",
|
||||||
|
"transaction_type": "Invoice",
|
||||||
|
"company_division": "L3Harris Technologies Communication Systems",
|
||||||
|
"address_1": "100 N Riverside Plaza",
|
||||||
|
"address_2": "",
|
||||||
|
"city": "Chicago",
|
||||||
|
"province": "IL",
|
||||||
|
"region": "Illinois",
|
||||||
|
"postal_code": "60606",
|
||||||
|
"is_primary": false,
|
||||||
|
"source_date": "2025-02-06",
|
||||||
|
"source_description": "Source from Sole Source",
|
||||||
|
"grant_type": "",
|
||||||
|
"description": "Battlefield management software suite",
|
||||||
|
"amount": 98787185.46,
|
||||||
|
"recipient": "US Navy",
|
||||||
|
"commodity_class": "Surveillance",
|
||||||
|
"contract_number": "CONT-9738-C",
|
||||||
|
"comments": "Includes technology transfer and local production",
|
||||||
|
"created_at": "2025-07-02T19:31:13.337006"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"transaction_id": 3,
|
||||||
|
"transaction_no": "2020-28186",
|
||||||
|
"transaction_type": "Purchase Order",
|
||||||
|
"company_division": "Elbit Systems Land Systems",
|
||||||
|
"address_1": "100 N Riverside Plaza",
|
||||||
|
"address_2": "",
|
||||||
|
"city": "Chicago",
|
||||||
|
"province": "IL",
|
||||||
|
"region": "Illinois",
|
||||||
|
"postal_code": "60606",
|
||||||
|
"is_primary": true,
|
||||||
|
"source_date": "2024-08-04",
|
||||||
|
"source_description": "Source from Direct Award",
|
||||||
|
"grant_type": "Type IV",
|
||||||
|
"description": "Satellite communications terminals",
|
||||||
|
"amount": 40307275.77,
|
||||||
|
"recipient": "Australian Defence Force",
|
||||||
|
"commodity_class": "Logistics",
|
||||||
|
"contract_number": "SUMMARY",
|
||||||
|
"comments": "Urgent operational requirement for deployed forces",
|
||||||
|
"created_at": "2025-07-02T19:31:13.339737"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"transaction_id": 4,
|
||||||
|
"transaction_no": "2024-46146",
|
||||||
|
"transaction_type": "Purchase Order",
|
||||||
|
"company_division": "Thales Group Defense & Security",
|
||||||
|
"address_1": "100 N Riverside Plaza",
|
||||||
|
"address_2": "",
|
||||||
|
"city": "Chicago",
|
||||||
|
"province": "IL",
|
||||||
|
"region": "Illinois",
|
||||||
|
"postal_code": "60606",
|
||||||
|
"is_primary": true,
|
||||||
|
"source_date": "2024-09-30",
|
||||||
|
"source_description": "Source from Direct Award",
|
||||||
|
"grant_type": "Type II",
|
||||||
|
"description": "Naval vessel propulsion components",
|
||||||
|
"amount": 42033336.08,
|
||||||
|
"recipient": "UK Ministry of Defence",
|
||||||
|
"commodity_class": "Cybersecurity",
|
||||||
|
"contract_number": "CONT-4826-D",
|
||||||
|
"comments": "Replaces aging legacy systems currently in service",
|
||||||
|
"created_at": "2025-07-02T19:31:13.342318"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"transaction_id": 5,
|
||||||
|
"transaction_no": "2020-45049",
|
||||||
|
"transaction_type": "Purchase Order",
|
||||||
|
"company_division": "Northrop Grumman Mission Systems",
|
||||||
|
"address_1": "1025 W NASA Boulevard",
|
||||||
|
"address_2": "",
|
||||||
|
"city": "Melbourne",
|
||||||
|
"province": "FL",
|
||||||
|
"region": "Florida",
|
||||||
|
"postal_code": "32919",
|
||||||
|
"is_primary": false,
|
||||||
|
"source_date": "2024-11-27",
|
||||||
|
"source_description": "Source from Framework Agreement",
|
||||||
|
"grant_type": "",
|
||||||
|
"description": "Aegis Combat System software updates",
|
||||||
|
"amount": 64502031.29,
|
||||||
|
"recipient": "UK Ministry of Defence",
|
||||||
|
"commodity_class": "Protective Equipment",
|
||||||
|
"contract_number": "SUMMARY",
|
||||||
|
"comments": "Subcontract with Leidos, US, through CAE Defense & Security. In support of the High Accuracy Detection and Exploitation System (HADES) program.",
|
||||||
|
"created_at": "2025-07-02T19:31:13.344968"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"transaction_id": 6,
|
||||||
|
"transaction_no": "2021-99601",
|
||||||
|
"transaction_type": "Subcontract",
|
||||||
|
"company_division": "MBDA Missile Systems",
|
||||||
|
"address_1": "870 Winter Street",
|
||||||
|
"address_2": "",
|
||||||
|
"city": "Waltham",
|
||||||
|
"province": "MA",
|
||||||
|
"region": "Massachusetts",
|
||||||
|
"postal_code": "02451",
|
||||||
|
"is_primary": false,
|
||||||
|
"source_date": "2022-10-08",
|
||||||
|
"source_description": "Source from Competitive Bid",
|
||||||
|
"grant_type": "Type I",
|
||||||
|
"description": "Long-range precision fires development",
|
||||||
|
"amount": 10778323.11,
|
||||||
|
"recipient": "US Marine Corps",
|
||||||
|
"commodity_class": "Software",
|
||||||
|
"contract_number": "CONT-8144-A",
|
||||||
|
"comments": "Replaces aging legacy systems currently in service",
|
||||||
|
"created_at": "2025-07-02T19:31:13.347491"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"transaction_id": 7,
|
||||||
|
"transaction_no": "2021-99541",
|
||||||
|
"transaction_type": "Subcontract",
|
||||||
|
"company_division": "Northrop Grumman Mission Systems",
|
||||||
|
"address_1": "1101 Wilson Boulevard",
|
||||||
|
"address_2": "Suite 2000",
|
||||||
|
"city": "Arlington",
|
||||||
|
"province": "VA",
|
||||||
|
"region": "Virginia",
|
||||||
|
"postal_code": "22209",
|
||||||
|
"is_primary": false,
|
||||||
|
"source_date": "2023-02-22",
|
||||||
|
"source_description": "Source from Direct Award",
|
||||||
|
"grant_type": "Type III",
|
||||||
|
"description": "THAAD missile defense interceptors",
|
||||||
|
"amount": 53622255.95,
|
||||||
|
"recipient": "German Armed Forces",
|
||||||
|
"commodity_class": "Logistics",
|
||||||
|
"contract_number": "SUMMARY",
|
||||||
|
"comments": "Joint development program with international partners",
|
||||||
|
"created_at": "2025-07-02T19:31:13.351123"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"transaction_id": 8,
|
||||||
|
"transaction_no": "2021-42294",
|
||||||
|
"transaction_type": "Subcontract",
|
||||||
|
"company_division": "Thales Group Defense & Security",
|
||||||
|
"address_1": "100 N Riverside Plaza",
|
||||||
|
"address_2": "",
|
||||||
|
"city": "Chicago",
|
||||||
|
"province": "IL",
|
||||||
|
"region": "Illinois",
|
||||||
|
"postal_code": "60606",
|
||||||
|
"is_primary": false,
|
||||||
|
"source_date": "2023-06-13",
|
||||||
|
"source_description": "Source from Sole Source",
|
||||||
|
"grant_type": "Type II",
|
||||||
|
"description": "THAAD missile defense interceptors",
|
||||||
|
"amount": 67639798.11,
|
||||||
|
"recipient": "Australian Defence Force",
|
||||||
|
"commodity_class": "Electronics",
|
||||||
|
"contract_number": "SUMMARY",
|
||||||
|
"comments": "Replaces aging legacy systems currently in service",
|
||||||
|
"created_at": "2025-07-02T19:31:13.353980"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"transaction_id": 9,
|
||||||
|
"transaction_no": "2023-67502",
|
||||||
|
"transaction_type": "Subcontract",
|
||||||
|
"company_division": "BAE Systems Electronic Systems",
|
||||||
|
"address_1": "5585 Cote de Liesse",
|
||||||
|
"address_2": "P O Box 1800",
|
||||||
|
"city": "ST LAURENT",
|
||||||
|
"province": "QC",
|
||||||
|
"region": "Quebec",
|
||||||
|
"postal_code": "H4T 1G6",
|
||||||
|
"is_primary": false,
|
||||||
|
"source_date": "2023-09-07",
|
||||||
|
"source_description": "Source from Framework Agreement",
|
||||||
|
"grant_type": "Type III",
|
||||||
|
"description": "Command and control software development",
|
||||||
|
"amount": 59006974.62,
|
||||||
|
"recipient": "Australian Defence Force",
|
||||||
|
"commodity_class": "Logistics",
|
||||||
|
"contract_number": "CONT-8954-C",
|
||||||
|
"comments": "Follows successful completion of prototype testing phase",
|
||||||
|
"created_at": "2025-07-02T19:31:13.355840"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"transaction_id": 10,
|
||||||
|
"transaction_no": "2025-37332",
|
||||||
|
"transaction_type": "Contract",
|
||||||
|
"company_division": "Leonardo S.p.A. Helicopters",
|
||||||
|
"address_1": "Tour Carpe Diem",
|
||||||
|
"address_2": "31 Place des Corolles",
|
||||||
|
"city": "Courbevoie",
|
||||||
|
"province": "",
|
||||||
|
"region": "\u00cele-de-France",
|
||||||
|
"postal_code": "92400",
|
||||||
|
"is_primary": true,
|
||||||
|
"source_date": "2023-09-22",
|
||||||
|
"source_description": "Source from Framework Agreement",
|
||||||
|
"grant_type": "Type I",
|
||||||
|
"description": "F-35 Lightning II Joint Strike Fighter components",
|
||||||
|
"amount": 99293527.27,
|
||||||
|
"recipient": "US Space Force",
|
||||||
|
"commodity_class": "Electronics",
|
||||||
|
"contract_number": "SUMMARY",
|
||||||
|
"comments": "Follows successful completion of prototype testing phase",
|
||||||
|
"created_at": "2025-07-02T19:31:13.358113"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"transaction_id": 11,
|
||||||
|
"transaction_no": "2025-97753",
|
||||||
|
"transaction_type": "Memorandum of Understanding",
|
||||||
|
"company_division": "Saab AB Aeronautics",
|
||||||
|
"address_1": "870 Winter Street",
|
||||||
|
"address_2": "",
|
||||||
|
"city": "Waltham",
|
||||||
|
"province": "MA",
|
||||||
|
"region": "Massachusetts",
|
||||||
|
"postal_code": "02451",
|
||||||
|
"is_primary": false,
|
||||||
|
"source_date": "2023-12-31",
|
||||||
|
"source_description": "Source from Framework Agreement",
|
||||||
|
"grant_type": "Type II",
|
||||||
|
"description": "F-35 Lightning II Joint Strike Fighter components",
|
||||||
|
"amount": 80186777.36,
|
||||||
|
"recipient": "German Armed Forces",
|
||||||
|
"commodity_class": "Aerospace",
|
||||||
|
"contract_number": "SUMMARY",
|
||||||
|
"comments": "Follows successful completion of prototype testing phase",
|
||||||
|
"created_at": "2025-07-02T19:31:13.360869"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"transaction_id": 12,
|
||||||
|
"transaction_no": "2020-21874",
|
||||||
|
"transaction_type": "Purchase Order",
|
||||||
|
"company_division": "Thales Group Defense & Security",
|
||||||
|
"address_1": "Ottobrunn",
|
||||||
|
"address_2": "Willy-Messerschmitt-Str. 1",
|
||||||
|
"city": "Munich",
|
||||||
|
"province": "",
|
||||||
|
"region": "Bavaria",
|
||||||
|
"postal_code": "85521",
|
||||||
|
"is_primary": false,
|
||||||
|
"source_date": "2022-10-22",
|
||||||
|
"source_description": "Source from Framework Agreement",
|
||||||
|
"grant_type": "Type III",
|
||||||
|
"description": "THAAD missile defense interceptors",
|
||||||
|
"amount": 56116899.6,
|
||||||
|
"recipient": "French Armed Forces",
|
||||||
|
"commodity_class": "Defense",
|
||||||
|
"contract_number": "CONT-6498-B",
|
||||||
|
"comments": "Joint development program with international partners",
|
||||||
|
"created_at": "2025-07-02T19:31:13.363525"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"transaction_id": 13,
|
||||||
|
"transaction_no": "2021-77008",
|
||||||
|
"transaction_type": "Purchase Order",
|
||||||
|
"company_division": "CAE Inc Defense & Security",
|
||||||
|
"address_1": "5585 Cote de Liesse",
|
||||||
|
"address_2": "P O Box 1800",
|
||||||
|
"city": "ST LAURENT",
|
||||||
|
"province": "QC",
|
||||||
|
"region": "Quebec",
|
||||||
|
"postal_code": "H4T 1G6",
|
||||||
|
"is_primary": true,
|
||||||
|
"source_date": "2024-04-20",
|
||||||
|
"source_description": "Source from Direct Award",
|
||||||
|
"grant_type": "",
|
||||||
|
"description": "Ballistic missile early warning radar",
|
||||||
|
"amount": 68699920.25,
|
||||||
|
"recipient": "Israeli Defense Forces",
|
||||||
|
"commodity_class": "Naval Systems",
|
||||||
|
"contract_number": "CONT-5837-D",
|
||||||
|
"comments": "Includes technology transfer and local production",
|
||||||
|
"created_at": "2025-07-02T19:31:13.366312"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"transaction_id": 14,
|
||||||
|
"transaction_no": "2025-18493",
|
||||||
|
"transaction_type": "Grant",
|
||||||
|
"company_division": "BAE Systems Electronic Systems",
|
||||||
|
"address_1": "1025 W NASA Boulevard",
|
||||||
|
"address_2": "",
|
||||||
|
"city": "Melbourne",
|
||||||
|
"province": "FL",
|
||||||
|
"region": "Florida",
|
||||||
|
"postal_code": "32919",
|
||||||
|
"is_primary": true,
|
||||||
|
"source_date": "2023-05-31",
|
||||||
|
"source_description": "Source from RFP",
|
||||||
|
"grant_type": "Type I",
|
||||||
|
"description": "Aegis Combat System software updates",
|
||||||
|
"amount": 37067541.29,
|
||||||
|
"recipient": "NATO",
|
||||||
|
"commodity_class": "Naval Systems",
|
||||||
|
"contract_number": "SUMMARY",
|
||||||
|
"comments": "Part of larger modernization initiative",
|
||||||
|
"created_at": "2025-07-02T19:31:13.370023"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"transaction_id": 15,
|
||||||
|
"transaction_no": "2022-45237",
|
||||||
|
"transaction_type": "Grant",
|
||||||
|
"company_division": "Northrop Grumman Mission Systems",
|
||||||
|
"address_1": "5585 Cote de Liesse",
|
||||||
|
"address_2": "P O Box 1800",
|
||||||
|
"city": "ST LAURENT",
|
||||||
|
"province": "QC",
|
||||||
|
"region": "Quebec",
|
||||||
|
"postal_code": "H4T 1G6",
|
||||||
|
"is_primary": true,
|
||||||
|
"source_date": "2025-01-02",
|
||||||
|
"source_description": "Source from Direct Award",
|
||||||
|
"grant_type": "Type II",
|
||||||
|
"description": "Counter-UAS detection and defeat systems",
|
||||||
|
"amount": 17804484.99,
|
||||||
|
"recipient": "German Armed Forces",
|
||||||
|
"commodity_class": "Medical",
|
||||||
|
"contract_number": "CONT-3919-A",
|
||||||
|
"comments": "Follows successful completion of prototype testing phase",
|
||||||
|
"created_at": "2025-07-02T19:31:13.373169"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"transaction_id": 16,
|
||||||
|
"transaction_no": "2022-68024",
|
||||||
|
"transaction_type": "Memorandum of Understanding",
|
||||||
|
"company_division": "Rheinmetall Vehicle Systems",
|
||||||
|
"address_1": "5585 Cote de Liesse",
|
||||||
|
"address_2": "P O Box 1800",
|
||||||
|
"city": "ST LAURENT",
|
||||||
|
"province": "QC",
|
||||||
|
"region": "Quebec",
|
||||||
|
"postal_code": "H4T 1G6",
|
||||||
|
"is_primary": true,
|
||||||
|
"source_date": "2025-04-29",
|
||||||
|
"source_description": "Source from Sole Source",
|
||||||
|
"grant_type": "",
|
||||||
|
"description": "Tactical radio communication systems",
|
||||||
|
"amount": 56484890.19,
|
||||||
|
"recipient": "US Navy",
|
||||||
|
"commodity_class": "Munitions",
|
||||||
|
"contract_number": "SUMMARY",
|
||||||
|
"comments": "Joint development program with international partners",
|
||||||
|
"created_at": "2025-07-02T19:31:13.376183"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"documents": []
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import random
|
||||||
|
from faker import Faker
|
||||||
|
|
||||||
|
fake = Faker()
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
BASE_URL = "http://localhost:5000"
|
||||||
|
API_URL = f"{BASE_URL}/api/transaction"
|
||||||
|
NUM_TRANSACTIONS = 10
|
||||||
|
|
||||||
|
# Sample data choices
|
||||||
|
TRANSACTION_TYPES = ['Subcontract', 'Purchase Order', 'Invoice', 'Contract']
|
||||||
|
COMPANY_DIVISIONS = ['Defense Systems', 'Aerospace Products', 'Technology Services', 'Corporate']
|
||||||
|
COMMODITY_CLASSES = ['Aerospace', 'Defense', 'Electronics', 'Software', 'Services']
|
||||||
|
|
||||||
|
def generate_transaction():
|
||||||
|
"""Generates a single fake transaction."""
|
||||||
|
return {
|
||||||
|
"transaction_type": random.choice(TRANSACTION_TYPES),
|
||||||
|
"company_division": random.choice(COMPANY_DIVISIONS),
|
||||||
|
"address_1": fake.street_address(),
|
||||||
|
"address_2": fake.secondary_address(),
|
||||||
|
"city": fake.city(),
|
||||||
|
"province": fake.state_abbr(),
|
||||||
|
"region": fake.state(),
|
||||||
|
"postal_code": fake.zipcode(),
|
||||||
|
"is_primary": fake.boolean(),
|
||||||
|
"source_date": fake.date_between(start_date='-2y', end_date='today').isoformat(),
|
||||||
|
"source_description": fake.sentence(nb_words=10),
|
||||||
|
"grant_type": fake.word().capitalize(),
|
||||||
|
"description": fake.paragraph(nb_sentences=3),
|
||||||
|
"amount": round(random.uniform(1000.0, 500000.0), 2),
|
||||||
|
"recipient": fake.company(),
|
||||||
|
"commodity_class": random.choice(COMMODITY_CLASSES),
|
||||||
|
"contract_number": fake.bothify(text='??-####-####'),
|
||||||
|
"comments": fake.sentence()
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_test_transactions(count=10):
|
||||||
|
"""Generates a list of fake transactions."""
|
||||||
|
return [generate_transaction() for _ in range(count)]
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
headers = {'Content-Type': 'application/json'}
|
||||||
|
for _ in range(NUM_TRANSACTIONS):
|
||||||
|
transaction_data = generate_transaction()
|
||||||
|
try:
|
||||||
|
response = requests.post(API_URL, headers=headers, data=json.dumps(transaction_data))
|
||||||
|
if response.status_code == 201:
|
||||||
|
print(f"Successfully created transaction: {response.json().get('id')}")
|
||||||
|
else:
|
||||||
|
print(f"Failed to create transaction. Status code: {response.status_code}, Response: {response.text}")
|
||||||
|
except requests.exceptions.ConnectionError as e:
|
||||||
|
print(f"Connection to {API_URL} failed. Make sure the Flask application is running.")
|
||||||
|
break
|
||||||
|
print(json.dumps(get_test_transactions(), indent=4))
|
|
@ -0,0 +1,44 @@
|
||||||
|
import psycopg2
|
||||||
|
import os
|
||||||
|
|
||||||
|
def init_db():
|
||||||
|
# Database connection parameters
|
||||||
|
conn = psycopg2.connect(
|
||||||
|
host="192.168.1.119",
|
||||||
|
port=5433,
|
||||||
|
dbname="testdb",
|
||||||
|
user="testuser",
|
||||||
|
password="testpass"
|
||||||
|
)
|
||||||
|
conn.autocommit = True
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# Read schema file
|
||||||
|
with open('schema.sql', 'r') as f:
|
||||||
|
sql_script = f.read()
|
||||||
|
|
||||||
|
# Execute schema
|
||||||
|
cursor.execute(sql_script)
|
||||||
|
|
||||||
|
# Insert sample data
|
||||||
|
cursor.execute('''
|
||||||
|
INSERT INTO transactions (
|
||||||
|
transaction_type, company_division, address_1, address_2,
|
||||||
|
city, province, region, postal_code, is_primary, source_date, source_description,
|
||||||
|
grant_type, description, amount, recipient, commodity_class, contract_number, comments
|
||||||
|
) VALUES (
|
||||||
|
'Subcontract', 'C A E Inc', '5585 Cote de Liesse', 'P O Box 1800',
|
||||||
|
'ST LAURENT', 'QC', 'Quebec', 'H4T 1G6', true, '2023-08-23', 'Source Description',
|
||||||
|
'Grant Type', '7000XR Full Flight Simulator (FFS) in Global 6000/6500 configuration (subc)',
|
||||||
|
0.00, 'US Army', 'Aerospace', 'SUMMARY',
|
||||||
|
'Subcontract with Leidos, US, through CAE Defense & Security. In support of the High Accuracy Detection and Exploitation System (HADES) program.'
|
||||||
|
)
|
||||||
|
''')
|
||||||
|
|
||||||
|
cursor.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
print("Database initialized successfully with sample data.")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
init_db()
|
|
@ -0,0 +1,10 @@
|
||||||
|
Flask==2.2.2
|
||||||
|
psycopg2-binary==2.9.3
|
||||||
|
requests==2.28.1
|
||||||
|
Faker==15.3.3
|
||||||
|
gunicorn==20.1.0
|
||||||
|
Werkzeug==2.3.7
|
||||||
|
Jinja2==3.1.2
|
||||||
|
MarkupSafe==2.1.3
|
||||||
|
itsdangerous==2.1.2
|
||||||
|
click==8.1.7
|
|
@ -0,0 +1,45 @@
|
||||||
|
-- Drop tables if they exist
|
||||||
|
DROP TABLE IF EXISTS transaction_documents;
|
||||||
|
DROP TABLE IF EXISTS transactions;
|
||||||
|
|
||||||
|
-- Create transactions table
|
||||||
|
CREATE TABLE transactions (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
transaction_no SERIAL UNIQUE,
|
||||||
|
transaction_type VARCHAR(50),
|
||||||
|
company_division VARCHAR(100),
|
||||||
|
address_1 VARCHAR(255),
|
||||||
|
address_2 VARCHAR(255),
|
||||||
|
city VARCHAR(100),
|
||||||
|
province VARCHAR(100),
|
||||||
|
region VARCHAR(100),
|
||||||
|
postal_code VARCHAR(20),
|
||||||
|
is_primary BOOLEAN,
|
||||||
|
source_date DATE,
|
||||||
|
source_description TEXT,
|
||||||
|
grant_type VARCHAR(100),
|
||||||
|
description TEXT,
|
||||||
|
amount NUMERIC(15, 2),
|
||||||
|
recipient VARCHAR(255),
|
||||||
|
commodity_class VARCHAR(100),
|
||||||
|
contract_number VARCHAR(100),
|
||||||
|
comments TEXT,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Create table for document attachments
|
||||||
|
CREATE TABLE transaction_documents (
|
||||||
|
document_id SERIAL PRIMARY KEY,
|
||||||
|
transaction_id INTEGER REFERENCES transactions(id) ON DELETE CASCADE,
|
||||||
|
filename VARCHAR(255) NOT NULL,
|
||||||
|
file_path VARCHAR(500) NOT NULL,
|
||||||
|
document_type VARCHAR(100),
|
||||||
|
description TEXT,
|
||||||
|
note TEXT,
|
||||||
|
upload_date TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Create indexes for better performance
|
||||||
|
CREATE INDEX idx_transaction_no ON transactions(transaction_no);
|
||||||
|
CREATE INDEX idx_transaction_date ON transactions(source_date);
|
||||||
|
CREATE INDEX idx_transaction_documents ON transaction_documents(transaction_id);
|
|
@ -0,0 +1,183 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}API Documentation - Project Ploughshares{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<h2>Project Ploughshares API Documentation</h2>
|
||||||
|
<p class="lead">Base URL: <code>http://{{ server_name }}</code></p>
|
||||||
|
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3>Endpoints</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<h4 class="mt-3">1. List All Transactions</h4>
|
||||||
|
<div class="bg-light p-3 mb-3">
|
||||||
|
<p><strong>GET</strong> <code>/api/transactions</code></p>
|
||||||
|
|
||||||
|
<h5>Complete Example:</h5>
|
||||||
|
<pre class="bg-dark text-light p-2"><code id="getAllTransactions">curl -X GET "http://{{ server_name }}/api/transactions"</code></pre>
|
||||||
|
<button class="btn btn-sm btn-secondary" onclick="copyToClipboard('getAllTransactions')">Copy</button>
|
||||||
|
|
||||||
|
<h5>Response:</h5>
|
||||||
|
<pre class="bg-dark text-light p-2"><code>[
|
||||||
|
{
|
||||||
|
"transaction_id": 1,
|
||||||
|
"transaction_no": "78708",
|
||||||
|
"transaction_type": "Subcontract",
|
||||||
|
"company_division": "C A E Inc",
|
||||||
|
"amount": 0.00,
|
||||||
|
"recipient": "US Army",
|
||||||
|
"created_at": "2023-07-02T12:34:56.789012"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"transaction_id": 2,
|
||||||
|
"transaction_no": "78709",
|
||||||
|
"transaction_type": "Purchase Order",
|
||||||
|
"company_division": "Example Corp",
|
||||||
|
"amount": 1000.00,
|
||||||
|
"recipient": "Test Recipient",
|
||||||
|
"created_at": "2023-07-03T10:11:12.131415"
|
||||||
|
}
|
||||||
|
]</code></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4 class="mt-4">2. Get Transaction Details</h4>
|
||||||
|
<div class="bg-light p-3 mb-3">
|
||||||
|
<p><strong>GET</strong> <code>/api/transaction/{id}</code></p>
|
||||||
|
|
||||||
|
<h5>Complete Example:</h5>
|
||||||
|
<pre class="bg-dark text-light p-2"><code id="getTransaction">curl -X GET "http://{{ server_name }}/api/transaction/1"</code></pre>
|
||||||
|
<button class="btn btn-sm btn-secondary" onclick="copyToClipboard('getTransaction')">Copy</button>
|
||||||
|
|
||||||
|
<h5>Response:</h5>
|
||||||
|
<pre class="bg-dark text-light p-2"><code>{
|
||||||
|
"transaction": {
|
||||||
|
"transaction_id": 1,
|
||||||
|
"transaction_no": "78708",
|
||||||
|
"transaction_type": "Subcontract",
|
||||||
|
"company_division": "C A E Inc",
|
||||||
|
"address_1": "5585 Cote de Liesse",
|
||||||
|
"address_2": "P O Box 1800",
|
||||||
|
"city": "ST LAURENT",
|
||||||
|
"province": "QC",
|
||||||
|
"region": "Quebec",
|
||||||
|
"postal_code": "H4T 1G6",
|
||||||
|
"is_primary": true,
|
||||||
|
"source_date": "2023-08-23",
|
||||||
|
"source_description": "Source Description",
|
||||||
|
"description": "7000XR Full Flight Simulator (FFS) in Global 6000/6500 configuration (subc)",
|
||||||
|
"amount": 0.00,
|
||||||
|
"recipient": "US Army",
|
||||||
|
"commodity_class": "Aerospace",
|
||||||
|
"contract_number": "SUMMARY",
|
||||||
|
"comments": "Subcontract with Leidos, US, through CAE Defense & Security...",
|
||||||
|
"created_at": "2023-07-02T12:34:56.789012"
|
||||||
|
},
|
||||||
|
"documents": [
|
||||||
|
{
|
||||||
|
"document_id": 1,
|
||||||
|
"transaction_id": 1,
|
||||||
|
"filename": "78708_20240501.pdf",
|
||||||
|
"file_path": "1/78708_20240501.pdf",
|
||||||
|
"document_type": "Contract",
|
||||||
|
"description": "Contract document",
|
||||||
|
"note": "Original contract",
|
||||||
|
"upload_date": "2023-07-02T12:34:56.789012"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4 class="mt-4">3. Create New Transaction</h4>
|
||||||
|
<div class="bg-light p-3 mb-3">
|
||||||
|
<p><strong>POST</strong> <code>/api/transaction</code></p>
|
||||||
|
|
||||||
|
<h5>Complete Example:</h5>
|
||||||
|
<pre class="bg-dark text-light p-2"><code id="createTransaction">curl -X POST "http://{{ server_name }}/api/transaction" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"transaction_no": "12345",
|
||||||
|
"transaction_type": "Purchase Order",
|
||||||
|
"company_division": "Example Corp",
|
||||||
|
"description": "Test transaction",
|
||||||
|
"amount": 1000.00,
|
||||||
|
"recipient": "Test Recipient"
|
||||||
|
}'</code></pre>
|
||||||
|
<button class="btn btn-sm btn-secondary" onclick="copyToClipboard('createTransaction')">Copy</button>
|
||||||
|
|
||||||
|
<h5>Response:</h5>
|
||||||
|
<pre class="bg-dark text-light p-2"><code>{
|
||||||
|
"message": "Transaction created successfully",
|
||||||
|
"transaction_id": 2
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4 class="mt-4">4. Update Transaction</h4>
|
||||||
|
<div class="bg-light p-3 mb-3">
|
||||||
|
<p><strong>PUT</strong> <code>/api/transaction/{id}</code></p>
|
||||||
|
|
||||||
|
<h5>Complete Example:</h5>
|
||||||
|
<pre class="bg-dark text-light p-2"><code id="updateTransaction">curl -X PUT "http://{{ server_name }}/api/transaction/2" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"transaction_type": "Purchase Order",
|
||||||
|
"company_division": "Updated Corp",
|
||||||
|
"description": "Updated transaction",
|
||||||
|
"amount": 1500.00,
|
||||||
|
"recipient": "Updated Recipient"
|
||||||
|
}'</code></pre>
|
||||||
|
<button class="btn btn-sm btn-secondary" onclick="copyToClipboard('updateTransaction')">Copy</button>
|
||||||
|
|
||||||
|
<h5>Response:</h5>
|
||||||
|
<pre class="bg-dark text-light p-2"><code>{
|
||||||
|
"message": "Transaction updated successfully"
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4 class="mt-4">5. Delete Transaction</h4>
|
||||||
|
<div class="bg-light p-3 mb-3">
|
||||||
|
<p><strong>DELETE</strong> <code>/api/transaction/{id}</code></p>
|
||||||
|
|
||||||
|
<h5>Complete Example:</h5>
|
||||||
|
<pre class="bg-dark text-light p-2"><code id="deleteTransaction">curl -X DELETE "http://{{ server_name }}/api/transaction/3"</code></pre>
|
||||||
|
<button class="btn btn-sm btn-secondary" onclick="copyToClipboard('deleteTransaction')">Copy</button>
|
||||||
|
|
||||||
|
<h5>Response:</h5>
|
||||||
|
<pre class="bg-dark text-light p-2"><code>{
|
||||||
|
"message": "Transaction deleted successfully"
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script>
|
||||||
|
function copyToClipboard(elementId) {
|
||||||
|
const element = document.getElementById(elementId);
|
||||||
|
const text = element.textContent;
|
||||||
|
|
||||||
|
navigator.clipboard.writeText(text).then(
|
||||||
|
function() {
|
||||||
|
// Show a temporary success message instead of an alert
|
||||||
|
const button = document.querySelector(`button[onclick="copyToClipboard('${elementId}')"]`);
|
||||||
|
const originalText = button.textContent;
|
||||||
|
button.textContent = "Copied!";
|
||||||
|
button.classList.remove("btn-secondary");
|
||||||
|
button.classList.add("btn-success");
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
button.textContent = originalText;
|
||||||
|
button.classList.remove("btn-success");
|
||||||
|
button.classList.add("btn-secondary");
|
||||||
|
}, 1500);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,109 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{% block title %}Project Ploughshares - Transaction Management System{% endblock %}</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
padding-top: 20px;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
border-bottom: 1px solid #e5e5e5;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
.header h3 {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
line-height: 40px;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
padding-top: 19px;
|
||||||
|
color: #777;
|
||||||
|
border-top: 1px solid #e5e5e5;
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
.document-card {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
.logo {
|
||||||
|
max-height: 40px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
.navbar-brand {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.currency-value {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #28a745;
|
||||||
|
}
|
||||||
|
.amount-cell {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
td:has(.currency-value) {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.version {
|
||||||
|
font-size: 0.8em;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header clearfix">
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<a class="navbar-brand" href="{{ url_for('index') }}">
|
||||||
|
<span class="fw-bold text-primary">Project Ploughshares</span> - Transaction Management
|
||||||
|
</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarNav">
|
||||||
|
<ul class="navbar-nav">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{{ url_for('index') }}">Home</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{{ url_for('create_transaction') }}">New Transaction</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{{ url_for('api_docs') }}">API Documentation</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% with messages = get_flashed_messages() %}
|
||||||
|
{% if messages %}
|
||||||
|
<div class="alert alert-info" role="alert">
|
||||||
|
{% for message in messages %}
|
||||||
|
{{ message }}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer class="footer">
|
||||||
|
<p>© 2023 Project Ploughshares - Transaction Management System <span class="version">v{{ version }}</span></p>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
{% block scripts %}{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,83 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Transactions - Project Ploughshares{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container-fluid mt-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
|
<h2>Transactions</h2>
|
||||||
|
<a href="{{ url_for('create_transaction') }}" class="btn btn-success">
|
||||||
|
<i class="bi bi-plus-lg"></i> New Transaction
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover">
|
||||||
|
<thead class="thead-light">
|
||||||
|
<tr>
|
||||||
|
<th>Transaction No.</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Division</th>
|
||||||
|
<th class="amount-cell">Amount</th>
|
||||||
|
<th>Source Date</th>
|
||||||
|
<th>Recipient</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for transaction in transactions %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ transaction['transaction_no'] }}</td>
|
||||||
|
<td>{{ transaction['transaction_type'] }}</td>
|
||||||
|
<td>{{ transaction['company_division'] }}</td>
|
||||||
|
<td class="amount-cell"><span class="currency-value">{{ transaction['amount']|currency }}</span></td>
|
||||||
|
<td>{{ transaction['source_date'].strftime('%Y-%m-%d') if transaction['source_date'] else 'N/A' }}</td>
|
||||||
|
<td>{{ transaction['recipient'] }}</td>
|
||||||
|
<td>
|
||||||
|
<a href="{{ url_for('view_transaction', id=transaction['id']) }}" class="btn btn-sm btn-info">
|
||||||
|
<i class="bi bi-eye"></i>
|
||||||
|
</a>
|
||||||
|
<a href="{{ url_for('update_transaction', id=transaction['id']) }}" class="btn btn-sm btn-warning">
|
||||||
|
<i class="bi bi-pencil"></i>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const searchInput = document.getElementById('searchInput');
|
||||||
|
const searchButton = document.getElementById('searchButton');
|
||||||
|
const tableRows = document.querySelectorAll('tbody tr');
|
||||||
|
|
||||||
|
function filterTable() {
|
||||||
|
const searchTerm = searchInput.value.toLowerCase();
|
||||||
|
|
||||||
|
tableRows.forEach(row => {
|
||||||
|
const text = row.textContent.toLowerCase();
|
||||||
|
if (text.includes(searchTerm)) {
|
||||||
|
row.style.display = '';
|
||||||
|
} else {
|
||||||
|
row.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
searchButton.addEventListener('click', filterTable);
|
||||||
|
searchInput.addEventListener('keyup', function(e) {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
filterTable();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,102 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}{% if transaction %}Edit{% else %}New{% endif %} Transaction - Project Ploughshares{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container mt-5">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3>{% if transaction %}Edit{% else %}New{% endif %} Transaction</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form action="{{ url_for('create_transaction') if not transaction else url_for('update_transaction', id=transaction.id) }}" method="post" class="needs-validation" novalidate>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="transaction_type">Transaction Type</label>
|
||||||
|
<input type="text" class="form-control" id="transaction_type" name="transaction_type" value="{{ transaction.transaction_type if transaction else '' }}" required>
|
||||||
|
<div class="invalid-feedback">Please enter a transaction type.</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="company_division">Company Division</label>
|
||||||
|
<input type="text" class="form-control" id="company_division" name="company_division" value="{{ transaction.company_division if transaction else '' }}" required>
|
||||||
|
<div class="invalid-feedback">Please enter a company division.</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="address_1">Address 1</label>
|
||||||
|
<input type="text" class="form-control" id="address_1" name="address_1" value="{{ transaction.address_1 if transaction else '' }}">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="address_2">Address 2</label>
|
||||||
|
<input type="text" class="form-control" id="address_2" name="address_2" value="{{ transaction.address_2 if transaction else '' }}">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="city">City</label>
|
||||||
|
<input type="text" class="form-control" id="city" name="city" value="{{ transaction.city if transaction else '' }}">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="province">Province</label>
|
||||||
|
<input type="text" class="form-control" id="province" name="province" value="{{ transaction.province if transaction else '' }}">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="region">Region</label>
|
||||||
|
<input type="text" class="form-control" id="region" name="region" value="{{ transaction.region if transaction else '' }}">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="postal_code">Postal Code</label>
|
||||||
|
<input type="text" class="form-control" id="postal_code" name="postal_code" value="{{ transaction.postal_code if transaction else '' }}">
|
||||||
|
</div>
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" id="is_primary" name="is_primary" {% if transaction and transaction.is_primary %}checked{% endif %}>
|
||||||
|
<label class="form-check-label" for="is_primary">Is Primary</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="source_date">Source Date</label>
|
||||||
|
<input type="date" class="form-control" id="source_date" name="source_date" value="{{ transaction.source_date.strftime('%Y-%m-%d') if transaction and transaction.source_date else '' }}">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="source_description">Source Description</label>
|
||||||
|
<textarea class="form-control" id="source_description" name="source_description" rows="3">{{ transaction.source_description if transaction else '' }}</textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="grant_type">Grant Type</label>
|
||||||
|
<input type="text" class="form-control" id="grant_type" name="grant_type" value="{{ transaction.grant_type if transaction else '' }}">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="description">Description</label>
|
||||||
|
<textarea class="form-control" id="description" name="description" rows="3">{{ transaction.description if transaction else '' }}</textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="amount">Amount</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text">$</span>
|
||||||
|
<input type="number" step="0.01" min="0" class="form-control" id="amount" name="amount" value="{{ transaction.amount if transaction else '' }}" placeholder="0.00">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="recipient">Recipient</label>
|
||||||
|
<input type="text" class="form-control" id="recipient" name="recipient" value="{{ transaction.recipient if transaction else '' }}">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="commodity_class">Commodity Class</label>
|
||||||
|
<input type="text" class="form-control" id="commodity_class" name="commodity_class" value="{{ transaction.commodity_class if transaction else '' }}">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="contract_number">Contract Number</label>
|
||||||
|
<input type="text" class="form-control" id="contract_number" name="contract_number" value="{{ transaction.contract_number if transaction else '' }}">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="comments">Comments</label>
|
||||||
|
<textarea class="form-control" id="comments" name="comments" rows="3">{{ transaction.comments if transaction else '' }}</textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary mt-3">Submit</button>
|
||||||
|
<a href="{{ url_for('index') }}" class="btn btn-secondary mt-3">Cancel</a>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,95 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Transaction {{ transaction.transaction_no }} - Project Ploughshares{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container-fluid mt-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
|
<h2>Transaction #{{ transaction.transaction_no }} - Project Ploughshares</h2>
|
||||||
|
<div>
|
||||||
|
<a href="{{ url_for('update_transaction', id=transaction.id) }}" class="btn btn-warning">
|
||||||
|
<i class="bi bi-pencil"></i> Edit
|
||||||
|
</a>
|
||||||
|
<a href="{{ url_for('index') }}" class="btn btn-secondary">
|
||||||
|
<i class="bi bi-arrow-left"></i> Back to List
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<ul class="nav nav-tabs" id="myTab" role="tablist">
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link active" id="details-tab" data-bs-toggle="tab" data-bs-target="#details" type="button" role="tab" aria-controls="details" aria-selected="true">Details</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="tab-content" id="myTabContent">
|
||||||
|
<div class="tab-pane fade show active" id="details" role="tabpanel" aria-labelledby="details-tab">
|
||||||
|
<div class="p-3">
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th style="width: 20%;">Transaction Type:</th>
|
||||||
|
<td>{{ transaction.transaction_type }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Company/Division:</th>
|
||||||
|
<td>{{ transaction.company_division }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Address:</th>
|
||||||
|
<td>
|
||||||
|
{{ transaction.address_1 }}<br>
|
||||||
|
{% if transaction.address_2 %}{{ transaction.address_2 }}<br>{% endif %}
|
||||||
|
{{ transaction.city }}, {{ transaction.province }}, {{ transaction.postal_code }}<br>
|
||||||
|
{{ transaction.region }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Primary:</th>
|
||||||
|
<td>{{ 'Yes' if transaction.is_primary else 'No' }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Source Date:</th>
|
||||||
|
<td>{{ transaction.source_date.strftime('%Y-%m-%d') if transaction.source_date else 'N/A' }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Source Description:</th>
|
||||||
|
<td>{{ transaction.source_description }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Grant Type:</th>
|
||||||
|
<td>{{ transaction.grant_type }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Description:</th>
|
||||||
|
<td>{{ transaction.description }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Amount:</th>
|
||||||
|
<td class="amount-cell"><span class="currency-value">{{ transaction.amount|currency }}</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Recipient:</th>
|
||||||
|
<td>{{ transaction.recipient }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Commodity Class:</th>
|
||||||
|
<td>{{ transaction.commodity_class }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Contract Number:</th>
|
||||||
|
<td>{{ transaction.contract_number }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Comments:</th>
|
||||||
|
<td>{{ transaction.comments }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,55 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# versionbump.sh - Script to bump version numbers in the VERSION file
|
||||||
|
# Usage: ./versionbump.sh [major|minor|patch]
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
VERSION_FILE="VERSION"
|
||||||
|
|
||||||
|
# Check if VERSION file exists
|
||||||
|
if [ ! -f "$VERSION_FILE" ]; then
|
||||||
|
echo "Error: $VERSION_FILE does not exist."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Read current version
|
||||||
|
CURRENT_VERSION=$(cat "$VERSION_FILE")
|
||||||
|
echo "Current version: $CURRENT_VERSION"
|
||||||
|
|
||||||
|
# Split version into components
|
||||||
|
IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT_VERSION"
|
||||||
|
|
||||||
|
# Check which part to bump
|
||||||
|
case "$1" in
|
||||||
|
major)
|
||||||
|
MAJOR=$((MAJOR + 1))
|
||||||
|
MINOR=0
|
||||||
|
PATCH=0
|
||||||
|
;;
|
||||||
|
minor)
|
||||||
|
MINOR=$((MINOR + 1))
|
||||||
|
PATCH=0
|
||||||
|
;;
|
||||||
|
patch)
|
||||||
|
PATCH=$((PATCH + 1))
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: $0 [major|minor|patch]"
|
||||||
|
echo "Example: $0 minor"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Create new version string
|
||||||
|
NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}"
|
||||||
|
echo "New version: $NEW_VERSION"
|
||||||
|
|
||||||
|
# Update VERSION file
|
||||||
|
echo "$NEW_VERSION" > "$VERSION_FILE"
|
||||||
|
echo "Updated $VERSION_FILE to $NEW_VERSION"
|
||||||
|
|
||||||
|
# Optional: Update other files that might need version updates
|
||||||
|
# Add commands here to update version in other files if needed
|
||||||
|
|
||||||
|
echo "Version bump complete!"
|
Loading…
Reference in New Issue