update: Major Overhault to all subsystems
This commit is contained in:
353
backend/crm/models.py
Normal file
353
backend/crm/models.py
Normal file
@@ -0,0 +1,353 @@
|
||||
from enum import Enum
|
||||
from typing import List, Optional
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ProductCategory(str, Enum):
|
||||
controller = "controller"
|
||||
striker = "striker"
|
||||
clock = "clock"
|
||||
part = "part"
|
||||
repair_service = "repair_service"
|
||||
|
||||
|
||||
class CostLineItem(BaseModel):
|
||||
name: str
|
||||
quantity: float = 1
|
||||
price: float = 0.0
|
||||
|
||||
|
||||
class ProductCosts(BaseModel):
|
||||
labor_hours: Optional[float] = None
|
||||
labor_rate: Optional[float] = None
|
||||
items: List[CostLineItem] = []
|
||||
total: Optional[float] = None
|
||||
|
||||
|
||||
class ProductStock(BaseModel):
|
||||
on_hand: int = 0
|
||||
reserved: int = 0
|
||||
available: int = 0
|
||||
|
||||
|
||||
class ProductCreate(BaseModel):
|
||||
name: str
|
||||
sku: Optional[str] = None
|
||||
category: ProductCategory
|
||||
description: Optional[str] = None
|
||||
price: float
|
||||
currency: str = "EUR"
|
||||
costs: Optional[ProductCosts] = None
|
||||
stock: Optional[ProductStock] = None
|
||||
active: bool = True
|
||||
status: str = "active" # active | discontinued | planned
|
||||
photo_url: Optional[str] = None
|
||||
|
||||
|
||||
class ProductUpdate(BaseModel):
|
||||
name: Optional[str] = None
|
||||
sku: Optional[str] = None
|
||||
category: Optional[ProductCategory] = None
|
||||
description: Optional[str] = None
|
||||
price: Optional[float] = None
|
||||
currency: Optional[str] = None
|
||||
costs: Optional[ProductCosts] = None
|
||||
stock: Optional[ProductStock] = None
|
||||
active: Optional[bool] = None
|
||||
status: Optional[str] = None
|
||||
photo_url: Optional[str] = None
|
||||
|
||||
|
||||
class ProductInDB(ProductCreate):
|
||||
id: str
|
||||
created_at: str
|
||||
updated_at: str
|
||||
|
||||
|
||||
class ProductListResponse(BaseModel):
|
||||
products: List[ProductInDB]
|
||||
total: int
|
||||
|
||||
|
||||
# ── Customers ────────────────────────────────────────────────────────────────
|
||||
|
||||
class ContactType(str, Enum):
|
||||
email = "email"
|
||||
phone = "phone"
|
||||
whatsapp = "whatsapp"
|
||||
other = "other"
|
||||
|
||||
|
||||
class CustomerContact(BaseModel):
|
||||
type: ContactType
|
||||
label: str
|
||||
value: str
|
||||
primary: bool = False
|
||||
|
||||
|
||||
class CustomerNote(BaseModel):
|
||||
text: str
|
||||
by: str
|
||||
at: str
|
||||
|
||||
|
||||
class OwnedItemType(str, Enum):
|
||||
console_device = "console_device"
|
||||
product = "product"
|
||||
freetext = "freetext"
|
||||
|
||||
|
||||
class OwnedItem(BaseModel):
|
||||
type: OwnedItemType
|
||||
# console_device fields
|
||||
device_id: Optional[str] = None
|
||||
label: Optional[str] = None
|
||||
# product fields
|
||||
product_id: Optional[str] = None
|
||||
product_name: Optional[str] = None
|
||||
quantity: Optional[int] = None
|
||||
serial_numbers: Optional[List[str]] = None
|
||||
# freetext fields
|
||||
description: Optional[str] = None
|
||||
serial_number: Optional[str] = None
|
||||
notes: Optional[str] = None
|
||||
|
||||
|
||||
class CustomerLocation(BaseModel):
|
||||
city: Optional[str] = None
|
||||
country: Optional[str] = None
|
||||
region: Optional[str] = None
|
||||
|
||||
|
||||
class CustomerCreate(BaseModel):
|
||||
title: Optional[str] = None
|
||||
name: str
|
||||
surname: Optional[str] = None
|
||||
organization: Optional[str] = None
|
||||
contacts: List[CustomerContact] = []
|
||||
notes: List[CustomerNote] = []
|
||||
location: Optional[CustomerLocation] = None
|
||||
language: str = "el"
|
||||
tags: List[str] = []
|
||||
owned_items: List[OwnedItem] = []
|
||||
linked_user_ids: List[str] = []
|
||||
nextcloud_folder: Optional[str] = None
|
||||
folder_id: Optional[str] = None # Human-readable Nextcloud folder name, e.g. "saint-john-corfu"
|
||||
|
||||
|
||||
class CustomerUpdate(BaseModel):
|
||||
title: Optional[str] = None
|
||||
name: Optional[str] = None
|
||||
surname: Optional[str] = None
|
||||
organization: Optional[str] = None
|
||||
contacts: Optional[List[CustomerContact]] = None
|
||||
notes: Optional[List[CustomerNote]] = None
|
||||
location: Optional[CustomerLocation] = None
|
||||
language: Optional[str] = None
|
||||
tags: Optional[List[str]] = None
|
||||
owned_items: Optional[List[OwnedItem]] = None
|
||||
linked_user_ids: Optional[List[str]] = None
|
||||
nextcloud_folder: Optional[str] = None
|
||||
# folder_id intentionally excluded from update — set once at creation
|
||||
|
||||
|
||||
class CustomerInDB(CustomerCreate):
|
||||
id: str
|
||||
created_at: str
|
||||
updated_at: str
|
||||
|
||||
|
||||
class CustomerListResponse(BaseModel):
|
||||
customers: List[CustomerInDB]
|
||||
total: int
|
||||
|
||||
|
||||
# ── Orders ───────────────────────────────────────────────────────────────────
|
||||
|
||||
class OrderStatus(str, Enum):
|
||||
draft = "draft"
|
||||
confirmed = "confirmed"
|
||||
in_production = "in_production"
|
||||
shipped = "shipped"
|
||||
delivered = "delivered"
|
||||
cancelled = "cancelled"
|
||||
|
||||
|
||||
class PaymentStatus(str, Enum):
|
||||
pending = "pending"
|
||||
partial = "partial"
|
||||
paid = "paid"
|
||||
|
||||
|
||||
class OrderDiscount(BaseModel):
|
||||
type: str # "percentage" | "fixed"
|
||||
value: float = 0
|
||||
reason: Optional[str] = None
|
||||
|
||||
|
||||
class OrderShipping(BaseModel):
|
||||
method: Optional[str] = None
|
||||
tracking_number: Optional[str] = None
|
||||
carrier: Optional[str] = None
|
||||
shipped_at: Optional[str] = None
|
||||
delivered_at: Optional[str] = None
|
||||
destination: Optional[str] = None
|
||||
|
||||
|
||||
class OrderItem(BaseModel):
|
||||
type: str # console_device | product | freetext
|
||||
product_id: Optional[str] = None
|
||||
product_name: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
quantity: int = 1
|
||||
unit_price: float = 0.0
|
||||
serial_numbers: List[str] = []
|
||||
|
||||
|
||||
class OrderCreate(BaseModel):
|
||||
customer_id: str
|
||||
order_number: Optional[str] = None
|
||||
status: OrderStatus = OrderStatus.draft
|
||||
items: List[OrderItem] = []
|
||||
subtotal: float = 0
|
||||
discount: Optional[OrderDiscount] = None
|
||||
total_price: float = 0
|
||||
currency: str = "EUR"
|
||||
shipping: Optional[OrderShipping] = None
|
||||
payment_status: PaymentStatus = PaymentStatus.pending
|
||||
invoice_path: Optional[str] = None
|
||||
notes: Optional[str] = None
|
||||
|
||||
|
||||
class OrderUpdate(BaseModel):
|
||||
customer_id: Optional[str] = None
|
||||
order_number: Optional[str] = None
|
||||
status: Optional[OrderStatus] = None
|
||||
items: Optional[List[OrderItem]] = None
|
||||
subtotal: Optional[float] = None
|
||||
discount: Optional[OrderDiscount] = None
|
||||
total_price: Optional[float] = None
|
||||
currency: Optional[str] = None
|
||||
shipping: Optional[OrderShipping] = None
|
||||
payment_status: Optional[PaymentStatus] = None
|
||||
invoice_path: Optional[str] = None
|
||||
notes: Optional[str] = None
|
||||
|
||||
|
||||
class OrderInDB(OrderCreate):
|
||||
id: str
|
||||
created_at: str
|
||||
updated_at: str
|
||||
|
||||
|
||||
class OrderListResponse(BaseModel):
|
||||
orders: List[OrderInDB]
|
||||
total: int
|
||||
|
||||
|
||||
# ── Comms Log ─────────────────────────────────────────────────────────────────
|
||||
|
||||
class CommType(str, Enum):
|
||||
email = "email"
|
||||
whatsapp = "whatsapp"
|
||||
call = "call"
|
||||
sms = "sms"
|
||||
note = "note"
|
||||
in_person = "in_person"
|
||||
|
||||
|
||||
class CommDirection(str, Enum):
|
||||
inbound = "inbound"
|
||||
outbound = "outbound"
|
||||
internal = "internal"
|
||||
|
||||
|
||||
class CommAttachment(BaseModel):
|
||||
filename: str
|
||||
nextcloud_path: Optional[str] = None
|
||||
content_type: Optional[str] = None
|
||||
size: Optional[int] = None
|
||||
|
||||
|
||||
class CommCreate(BaseModel):
|
||||
customer_id: Optional[str] = None
|
||||
type: CommType
|
||||
mail_account: Optional[str] = None
|
||||
direction: CommDirection
|
||||
subject: Optional[str] = None
|
||||
body: Optional[str] = None
|
||||
body_html: Optional[str] = None
|
||||
attachments: List[CommAttachment] = []
|
||||
ext_message_id: Optional[str] = None
|
||||
from_addr: Optional[str] = None
|
||||
to_addrs: Optional[List[str]] = None
|
||||
logged_by: Optional[str] = None
|
||||
occurred_at: Optional[str] = None # defaults to now if not provided
|
||||
|
||||
|
||||
class CommUpdate(BaseModel):
|
||||
subject: Optional[str] = None
|
||||
body: Optional[str] = None
|
||||
occurred_at: Optional[str] = None
|
||||
|
||||
|
||||
class CommInDB(BaseModel):
|
||||
id: str
|
||||
customer_id: Optional[str] = None
|
||||
type: CommType
|
||||
mail_account: Optional[str] = None
|
||||
direction: CommDirection
|
||||
subject: Optional[str] = None
|
||||
body: Optional[str] = None
|
||||
body_html: Optional[str] = None
|
||||
attachments: List[CommAttachment] = []
|
||||
ext_message_id: Optional[str] = None
|
||||
from_addr: Optional[str] = None
|
||||
to_addrs: Optional[List[str]] = None
|
||||
logged_by: Optional[str] = None
|
||||
occurred_at: str
|
||||
created_at: str
|
||||
is_important: bool = False
|
||||
is_read: bool = False
|
||||
|
||||
|
||||
class CommListResponse(BaseModel):
|
||||
entries: List[CommInDB]
|
||||
total: int
|
||||
|
||||
|
||||
# ── Media ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
class MediaDirection(str, Enum):
|
||||
received = "received"
|
||||
sent = "sent"
|
||||
internal = "internal"
|
||||
|
||||
|
||||
class MediaCreate(BaseModel):
|
||||
customer_id: Optional[str] = None
|
||||
order_id: Optional[str] = None
|
||||
filename: str
|
||||
nextcloud_path: str
|
||||
mime_type: Optional[str] = None
|
||||
direction: Optional[MediaDirection] = None
|
||||
tags: List[str] = []
|
||||
uploaded_by: Optional[str] = None
|
||||
|
||||
|
||||
class MediaInDB(BaseModel):
|
||||
id: str
|
||||
customer_id: Optional[str] = None
|
||||
order_id: Optional[str] = None
|
||||
filename: str
|
||||
nextcloud_path: str
|
||||
mime_type: Optional[str] = None
|
||||
direction: Optional[MediaDirection] = None
|
||||
tags: List[str] = []
|
||||
uploaded_by: Optional[str] = None
|
||||
created_at: str
|
||||
|
||||
|
||||
class MediaListResponse(BaseModel):
|
||||
items: List[MediaInDB]
|
||||
total: int
|
||||
Reference in New Issue
Block a user