diff --git a/backend/app/data/data.py b/backend/app/data/data.py new file mode 100644 index 0000000..2788947 --- /dev/null +++ b/backend/app/data/data.py @@ -0,0 +1,86 @@ +from typing import List +from app.models import ContactPerson, Address, Dewar, Proposal, Shipment + +contacts: List[ContactPerson] = [ + ContactPerson(id=1, firstname="Frodo", lastname="Baggins", phone_number="123-456-7890", email="frodo.baggins@lotr.com"), + ContactPerson(id=1, firstname="Frodo", lastname="Baggins", phone_number="123-456-7890", email="frodo.baggins@lotr.com"), + ContactPerson(id=2, firstname="Samwise", lastname="Gamgee", phone_number="987-654-3210", email="samwise.gamgee@lotr.com"), + ContactPerson(id=3, firstname="Aragorn", lastname="Elessar", phone_number="123-333-4444", email="aragorn.elessar@lotr.com"), + ContactPerson(id=4, firstname="Legolas", lastname="Greenleaf", phone_number="555-666-7777", email="legolas.greenleaf@lotr.com"), + ContactPerson(id=5, firstname="Gimli", lastname="Son of Gloin", phone_number="888-999-0000", email="gimli.sonofgloin@lotr.com"), + ContactPerson(id=6, firstname="Gandalf", lastname="The Grey", phone_number="222-333-4444", email="gandalf.thegrey@lotr.com"), + ContactPerson(id=7, firstname="Boromir", lastname="Son of Denethor", phone_number="111-222-3333", email="boromir.sonofdenethor@lotr.com"), + ContactPerson(id=8, firstname="Galadriel", lastname="Lady of Lothlórien", phone_number="444-555-6666", email="galadriel.lothlorien@lotr.com"), + ContactPerson(id=9, firstname="Elrond", lastname="Half-elven", phone_number="777-888-9999", email="elrond.halfelven@lotr.com"), + ContactPerson(id=10, firstname="Eowyn", lastname="Shieldmaiden of Rohan", phone_number="000-111-2222", email="eowyn.rohan@lotr.com"), +] + +return_addresses: List[Address] = [ + Address(id=1, street='123 Hobbiton St', city='Shire', zipcode='12345', country='Middle Earth'), + Address(id=2, street='456 Rohan Rd', city='Edoras', zipcode='67890', country='Middle Earth'), + Address(id=3, street='789 Greenwood Dr', city='Mirkwood', zipcode='13579', country='Middle Earth'), + Address(id=4, street='321 Gondor Ave', city='Minas Tirith', zipcode='24680', country='Middle Earth'), + Address(id=5, street='654 Falgorn Pass', city='Rivendell', zipcode='11223', country='Middle Earth') +] + +dewars: List[Dewar] = [ + Dewar( + id='DEWAR001', dewar_name='Dewar One', tracking_number='TRACK123', number_of_pucks=7, number_of_samples=70, + return_address=[return_addresses[0]], contact_person=[contacts[0]], status='Ready for Shipping', + ready_date='2023-09-30', shipping_date='', arrival_date='', returning_date='', qrcode='QR123DEWAR001' + ), + Dewar( + id='DEWAR002', dewar_name='Dewar Two', tracking_number='TRACK124', number_of_pucks=3, number_of_samples=33, + return_address=[return_addresses[1]], contact_person=[contacts[1]], status='In Preparation', + ready_date='', shipping_date='', arrival_date='', returning_date='', qrcode='QR123DEWAR002' + ), + Dewar( + id='DEWAR003', dewar_name='Dewar Three', tracking_number='TRACK125', number_of_pucks=7, number_of_samples=72, + return_address=[return_addresses[0]], contact_person=[contacts[2]], status='Not Shipped', ready_date='2024.01.01', + shipping_date='', arrival_date='', returning_date='', qrcode='QR123DEWAR003' + ), + Dewar( + id='DEWAR004', dewar_name='Dewar Four', tracking_number='', number_of_pucks=7, number_of_samples=70, + return_address=[return_addresses[0]], contact_person=[contacts[2]], status='Delayed', ready_date='2024.01.01', + shipping_date='2024.01.02', arrival_date='', returning_date='', qrcode='QR123DEWAR003' + ), + Dewar( + id='DEWAR005', dewar_name='Dewar Five', tracking_number='', number_of_pucks=3, number_of_samples=30, + return_address=[return_addresses[0]], contact_person=[contacts[2]], status='Returned', ready_date='2024.01.01', + shipping_date='2024.01.02', arrival_date='2024.01.03', returning_date='2024.01.07', qrcode='QR123DEWAR003' + ), +] + +proposals: List[Proposal] = [ + Proposal(id=1, number="PROPOSAL-FRODO-001"), # "The Quest for the Ring" + Proposal(id=2, number="PROPOSAL-GANDALF-002"), # "The Fellowship's Journey" + Proposal(id=3, number="PROPOSAL-ARAGORN-003"), # "Return of the King" + Proposal(id=4, number="PROPOSAL-SAURON-004"), # "The Dark Lord's Plot" + Proposal(id=5, number="PROPOSAL-MORDOR-005"), # "The Road to Mount Doom" +] + +specific_dewar_ids1 = ['DEWAR003'] +specific_dewar_ids2 = ['DEWAR001', 'DEWAR002'] +specific_dewar_ids3 = ['DEWAR003', 'DEWAR004', 'DEWAR005'] + +specific_dewars1 = [dewar for dewar in dewars if dewar.id in specific_dewar_ids1] +specific_dewars2 = [dewar for dewar in dewars if dewar.id in specific_dewar_ids2] +specific_dewars3 = [dewar for dewar in dewars if dewar.id in specific_dewar_ids3] + +shipments = [ + Shipment( + shipment_id='SHIPMORDOR', shipment_date='2024-10-10', shipment_name='Shipment from Mordor', + shipment_status='Delivered', contact_person=[contacts[1]], proposal_number=[proposals[1]], + return_address=[return_addresses[0]], comments='Handle with care', dewars=specific_dewars1 + ), + Shipment( + shipment_id='SHIPMORDOR2', shipment_date='2024-10-24', shipment_name='Shipment from Mordor', + shipment_status='In Transit', contact_person=[contacts[3]], proposal_number=[proposals[2]], + return_address=[return_addresses[1]], comments='Contains the one ring', dewars=specific_dewars2 + ), + Shipment( + shipment_id='SHIPMORDOR3', shipment_date='2024-10-28', shipment_name='Shipment from Mordor', + shipment_status='In Transit', contact_person=[contacts[4]], proposal_number=[proposals[3]], + return_address=[return_addresses[0]], comments='Contains the one ring', dewars=specific_dewars3 + ) +] \ No newline at end of file diff --git a/backend/app/database.py b/backend/app/database.py new file mode 100644 index 0000000..06a7aec --- /dev/null +++ b/backend/app/database.py @@ -0,0 +1,24 @@ +from sqlalchemy import create_engine +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker + +DATABASE_URL = "sqlite:///./test.db" + +# Database setup +engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False}) +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + +Base = declarative_base() + +# Dependency +def get_db(): + db = SessionLocal() + try: + yield db + finally: + db.close() + +# Initialize the database +def init_db(): + import app.models # Import all models here for metadata.create_all() to recognize them + Base.metadata.create_all(bind=engine) \ No newline at end of file diff --git a/backend/app/main.py b/backend/app/main.py new file mode 100644 index 0000000..b058b30 --- /dev/null +++ b/backend/app/main.py @@ -0,0 +1,25 @@ +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +from app.routers import address, contact, proposal, dewar, shipment + +app = FastAPI() + +# Apply CORS middleware +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], # Enable CORS for all origins for now + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# Include your routers +app.include_router(contact.router, prefix="/contacts", tags=["contacts"]) +app.include_router(address.router, prefix="/return_addresses", tags=["return_addresses"]) +app.include_router(proposal.router, prefix="/proposals", tags=["proposals"]) +app.include_router(dewar.router, prefix="/dewars", tags=["dewars"]) +app.include_router(shipment.router, prefix="/shipments", tags=["shipments"]) + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="127.0.0.1", port=8000, log_level="debug") \ No newline at end of file diff --git a/backend/app/models.py b/backend/app/models.py new file mode 100644 index 0000000..5b13c1f --- /dev/null +++ b/backend/app/models.py @@ -0,0 +1,61 @@ +from pydantic import BaseModel +from typing import List, Optional + +class ContactPerson(BaseModel): + id: Optional[int] = None + firstname: str + lastname: str + phone_number: str + email: str + +class Address(BaseModel): + id: Optional[int] = None + street: str + city: str + zipcode: str + country: str + +class Proposal(BaseModel): + id: Optional[int] = None + number: str + +class Dewar(BaseModel): + id: Optional[str] = None + dewar_name: str + tracking_number: Optional[str] = None + number_of_pucks: int + number_of_samples: int + return_address: List[Address] + contact_person: List[ContactPerson] + status: str + ready_date: Optional[str] = None + shipping_date: Optional[str] = None + arrival_date: Optional[str] = None + returning_date: Optional[str] = None + qrcode: str + +class Shipment(BaseModel): + shipment_id: Optional[str] = None + shipment_name: str + shipment_date: str + shipment_status: str + contact_person: List[ContactPerson] + proposal_number: List[Proposal] + return_address: List[Address] + comments: Optional[str] = None + dewars: List[Dewar] + + def get_number_of_dewars(self) -> int: + return len(self.dewars) + + def get_shipment_contact_persons(self) -> List[ContactPerson]: + return self.contact_person + + def get_shipment_return_addresses(self) -> List[Address]: + return self.return_address + + def get_proposals(self) -> List[Proposal]: + return self.proposal_number + + class Config: + from_attributes = True \ No newline at end of file diff --git a/backend/app/routers/address.py b/backend/app/routers/address.py new file mode 100644 index 0000000..7c36513 --- /dev/null +++ b/backend/app/routers/address.py @@ -0,0 +1,26 @@ +from fastapi import APIRouter, HTTPException, status +from typing import List, Optional +from app.data.data import return_addresses +from app.models import Address # Import the Address model + + +router = APIRouter() + +@router.get("/", response_model=List[Address]) +async def get_return_addresses(): + return return_addresses + +@router.post("/", response_model=Address, status_code=status.HTTP_201_CREATED) +async def create_return_address(address: Address): + if any(a.city == address.city for a in return_addresses): + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Address in this city already exists." + ) + if return_addresses: + max_id = max(a.id for a in return_addresses) + address.id = max_id + 1 if address.id is None else address.id + else: + address.id = 1 if address.id is None else address.id + return_addresses.append(address) + return address \ No newline at end of file diff --git a/backend/app/routers/contact.py b/backend/app/routers/contact.py new file mode 100644 index 0000000..14e72d6 --- /dev/null +++ b/backend/app/routers/contact.py @@ -0,0 +1,26 @@ +# app/routers/contact.py +from fastapi import APIRouter, HTTPException, status +from typing import List +from app.data.data import contacts +from app.models import ContactPerson + +router = APIRouter() + +@router.get("/", response_model=List[ContactPerson]) +async def get_contacts(): + return contacts + +@router.post("/", response_model=ContactPerson, status_code=status.HTTP_201_CREATED) +async def create_contact(contact: ContactPerson): + if any(c.email == contact.email for c in contacts): + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="This contact already exists." + ) + if contacts: + max_id = max(c.id for c in contacts) + contact.id = max_id + 1 if contact.id is None else contact.id + else: + contact.id = 1 if contact.id is None else contact.id + contacts.append(contact) + return contact \ No newline at end of file diff --git a/backend/app/routers/dewar.py b/backend/app/routers/dewar.py new file mode 100644 index 0000000..9dac9aa --- /dev/null +++ b/backend/app/routers/dewar.py @@ -0,0 +1,19 @@ +from fastapi import APIRouter, HTTPException, status +from typing import List, Optional +import uuid +from app.data.data import dewars, contacts, return_addresses +from app.models import Dewar, ContactPerson, Address + + +router = APIRouter() + +@router.get("/", response_model=List[Dewar]) +async def get_dewars(): + return dewars + +@router.post("/", response_model=Dewar, status_code=status.HTTP_201_CREATED) +async def create_dewar(dewar: Dewar) -> Dewar: + dewar_id = f'DEWAR-{uuid.uuid4().hex[:8].upper()}' + dewar.id = dewar_id + dewars.append(dewar) + return dewar \ No newline at end of file diff --git a/backend/app/routers/proposal.py b/backend/app/routers/proposal.py new file mode 100644 index 0000000..49a3239 --- /dev/null +++ b/backend/app/routers/proposal.py @@ -0,0 +1,11 @@ +from fastapi import APIRouter +from typing import List, Optional +from app.data.data import proposals +from app.models import Proposal # Import the Address model + + +router = APIRouter() + +@router.get("/", response_model=List[Proposal]) +async def get_proposals(): + return proposals \ No newline at end of file diff --git a/backend/app/routers/shipment.py b/backend/app/routers/shipment.py new file mode 100644 index 0000000..d517398 --- /dev/null +++ b/backend/app/routers/shipment.py @@ -0,0 +1,85 @@ +from fastapi import APIRouter, HTTPException, status, Query +from typing import List, Optional +import uuid +from app.data.data import shipments, dewars +from app.models import Shipment, Dewar, ContactPerson, Address, Proposal + +router = APIRouter() + +@router.get("/", response_model=List[Shipment]) +async def get_shipments(shipment_id: Optional[str] = Query(None, description="ID of the specific shipment to retrieve")): + if shipment_id: + shipment = next((sh for sh in shipments if sh.shipment_id == shipment_id), None) + if not shipment: + raise HTTPException(status_code=404, detail="Shipment not found") + return [shipment] + return shipments + +@router.delete("/{shipment_id}", status_code=status.HTTP_204_NO_CONTENT) +async def delete_shipment(shipment_id: str): + global shipments + shipments = [shipment for shipment in shipments if shipment.shipment_id != shipment_id] + +@router.post("/{shipment_id}/add_dewar", response_model=Shipment) +async def add_dewar_to_shipment(shipment_id: str, dewar_id: str): + shipment = next((sh for sh in shipments if sh.shipment_id == shipment_id), None) + if not shipment: + raise HTTPException(status_code=404, detail="Shipment not found") + dewar = next((dw for dw in dewars if dw.id == dewar_id), None) + if not dewar: + raise HTTPException(status_code=404, detail="Dewar not found") + if dewar not in shipment.dewars: + shipment.dewars.append(dewar) + return shipment + +@router.put("/{shipment_id}", response_model=Shipment) +async def update_shipment(shipment_id: str, updated_shipment: Shipment): + global shipments + shipment = next((sh for sh in shipments if sh.shipment_id == shipment_id), None) + if not shipment: + raise HTTPException(status_code=404, detail="Shipment not found") + shipment.shipment_name = updated_shipment.shipment_name + shipment.shipment_date = updated_shipment.shipment_date + shipment.shipment_status = updated_shipment.shipment_status + shipment.contact_person = updated_shipment.contact_person + shipment.proposal_number = updated_shipment.proposal_number + shipment.return_address = updated_shipment.return_address + shipment.comments = updated_shipment.comments + + existing_dewar_dict = {dewar.id: dewar for dewar in shipment.dewars} + for updated_dewar in updated_shipment.dewars: + if updated_dewar.id in existing_dewar_dict: + existing_dewar_dict[updated_dewar.id].dewar_name = updated_dewar.dewar_name + existing_dewar_dict[updated_dewar.id].tracking_number = updated_dewar.tracking_number + existing_dewar_dict[updated_dewar.id].number_of_pucks = updated_dewar.number_of_pucks + existing_dewar_dict[updated_dewar.id].number_of_samples = updated_dewar.number_of_samples + existing_dewar_dict[updated_dewar.id].return_address = updated_dewar.return_address + existing_dewar_dict[updated_dewar.id].contact_person = updated_dewar.contact_person + existing_dewar_dict[updated_dewar.id].status = updated_dewar.status + existing_dewar_dict[updated_dewar.id].ready_date = updated_dewar.ready_date + existing_dewar_dict[updated_dewar.id].shipping_date = updated_dewar.shipping_date + existing_dewar_dict[updated_dewar.id].arrival_date = updated_dewar.arrival_date + existing_dewar_dict[updated_dewar.id].returning_date = updated_dewar.returning_date + existing_dewar_dict[updated_dewar.id].qrcode = updated_dewar.qrcode + else: + shipment.dewars.append(updated_dewar) + return shipment + +@router.post("/", response_model=Shipment, status_code=status.HTTP_201_CREATED) +async def create_shipment(shipment: Shipment): + shipment_id = f'SHIP-{uuid.uuid4().hex[:8].upper()}' + shipment.shipment_id = shipment_id + shipments.append(shipment) + return shipment + +@router.delete("/{shipment_id}/remove_dewar/{dewar_id}", response_model=Shipment) +async def remove_dewar_from_shipment(shipment_id: str, dewar_id: str): + shipment = next((sh for sh in shipments if sh.shipment_id == shipment_id), None) + if not shipment: + raise HTTPException(status_code=404, detail="Shipment not found") + shipment.dewars = [dw for dw in shipment.dewars if dw.id != dewar_id] + return shipment + +@router.get("/contact_persons", response_model=List[ContactPerson]) +async def get_shipment_contact_persons(): + return [{"shipment_id": shipment.shipment_id, "contact_person": shipment.get_shipment_contact_persons()} for shipment in shipments] \ No newline at end of file diff --git a/backend/app/schemas.py b/backend/app/schemas.py new file mode 100644 index 0000000..2047e8a --- /dev/null +++ b/backend/app/schemas.py @@ -0,0 +1,59 @@ +from pydantic import BaseModel +from typing import List, Optional + +class ContactPersonSchema(BaseModel): + id: Optional[int] + firstname: str + lastname: str + phone_number: str + email: str + + class Config: + from_attributes = True # Update here + +class AddressSchema(BaseModel): + id: Optional[int] + street: str + city: str + zipcode: str + country: str + + class Config: + from_attributes = True # Update here + +class DewarSchema(BaseModel): + id: Optional[str] + dewar_name: str + tracking_number: Optional[str] + number_of_pucks: int + number_of_samples: int + status: str + ready_date: Optional[str] + shipping_date: Optional[str] + arrival_date: Optional[str] + returning_date: Optional[str] + qrcode: str + + class Config: + from_attributes = True # Update here + +class ProposalSchema(BaseModel): + id: Optional[int] + number: str + + class Config: + from_attributes = True # Update here + +class ShipmentSchema(BaseModel): + shipment_id: Optional[str] + shipment_name: str + shipment_date: str + shipment_status: str + contact_person: List[ContactPersonSchema] + proposal_number: List[ProposalSchema] + return_address: List[AddressSchema] + comments: Optional[str] = None + dewars: List[DewarSchema] + + class Config: + from_attributes = True # Update here \ No newline at end of file diff --git a/backend/main.py b/backend/save/main.py similarity index 98% rename from backend/main.py rename to backend/save/main.py index 963ade7..9453dbb 100644 --- a/backend/main.py +++ b/backend/save/main.py @@ -111,6 +111,9 @@ contacts = [ return_addresses = [ Address(id=1, street='123 Hobbiton St', city='Shire', zipcode='12345', country='Middle Earth'), Address(id=2, street='456 Rohan Rd', city='Edoras', zipcode='67890', country='Middle Earth') + Address(id=3, street='789 Greenwood Dr', city='Mirkwood', zipcode='13579', country='Middle Earth'), + Address(id=4, street='321 Gondor Ave', city='Minas Tirith', zipcode='24680', country='Middle Earth'), + Address(id=5, street='654 Falgorn Pass', city='Rivendell', zipcode='11223', country='Middle Earth') ] # Example data for dewars