Spaces:
Running
feat(order): Add database migrations and refactor order models to use UUID
Browse files- Add database migration scripts for creating and managing ecommerce order tables
- Add rollback migration for reverting order table changes
- Add migration runner script for executing SQL migrations
- Refactor SalesOrder model to use UUID for all ID fields (customer_id, merchant_id)
- Remove branch_id field from SalesOrder model
- Update all DateTime columns to use timezone-aware DateTime(timezone=True)
- Remove foreign key constraints from SalesOrderItem and SalesOrderAddress models
- Manage relationships at application level instead of database level
- Fix customer_gstin field length constraint (String(15))
- Update ARRAY column types from String to Text for better compatibility
- Add explicit foreign_keys parameter to relationship definitions
- Improve merchant_id UUID conversion handling in OrderService
- Add comprehensive setup documentation for order table configuration
- SETUP_ORDER_TABLES.md +0 -0
- app/order/models/model.py +21 -20
- app/order/services/service.py +36 -14
- db/README.md +225 -0
- db/migrations/001_create_ecommerce_order_tables.sql +240 -0
- db/migrations/001_rollback_ecommerce_order_tables.sql +22 -0
- db/migrations/002_alter_existing_order_tables_to_uuid.sql +218 -0
- db/run_migration.py +129 -0
|
File without changes
|
|
@@ -1,8 +1,10 @@
|
|
| 1 |
"""
|
| 2 |
SQLAlchemy models for sales orders.
|
| 3 |
Maps to trans.sales_orders, trans.sales_order_items, and trans.sales_order_addresses tables.
|
|
|
|
|
|
|
| 4 |
"""
|
| 5 |
-
from sqlalchemy import Column, String, Integer, Numeric, DateTime, Text,
|
| 6 |
from sqlalchemy.dialects.postgresql import UUID
|
| 7 |
from sqlalchemy.orm import relationship
|
| 8 |
from datetime import datetime
|
|
@@ -18,18 +20,17 @@ class SalesOrder(Base):
|
|
| 18 |
|
| 19 |
sales_order_id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
| 20 |
order_number = Column(String(50), unique=True, nullable=False, index=True)
|
| 21 |
-
branch_id = Column(UUID(as_uuid=True), nullable=True)
|
| 22 |
merchant_id = Column(UUID(as_uuid=True), nullable=False, index=True)
|
| 23 |
-
order_date = Column(DateTime, nullable=False, default=datetime.utcnow)
|
| 24 |
status = Column(String(50), nullable=False, default="pending")
|
| 25 |
|
| 26 |
# Customer information
|
| 27 |
-
customer_id = Column(
|
| 28 |
customer_name = Column(String(255), nullable=True)
|
| 29 |
customer_type = Column(String(50), nullable=True, default="retail")
|
| 30 |
customer_phone = Column(String(20), nullable=True)
|
| 31 |
customer_email = Column(String(255), nullable=True)
|
| 32 |
-
customer_gstin = Column(String(
|
| 33 |
|
| 34 |
# Financial information
|
| 35 |
subtotal = Column(Numeric(15, 2), nullable=False, default=0.0)
|
|
@@ -45,7 +46,7 @@ class SalesOrder(Base):
|
|
| 45 |
payment_type = Column(String(50), nullable=True)
|
| 46 |
payment_status = Column(String(50), nullable=False, default="pending")
|
| 47 |
payment_method = Column(String(50), nullable=True)
|
| 48 |
-
payment_date = Column(DateTime, nullable=True)
|
| 49 |
payment_reference = Column(String(255), nullable=True)
|
| 50 |
amount_paid = Column(Numeric(15, 2), nullable=False, default=0.0)
|
| 51 |
amount_due = Column(Numeric(15, 2), nullable=False, default=0.0)
|
|
@@ -54,13 +55,13 @@ class SalesOrder(Base):
|
|
| 54 |
|
| 55 |
# Fulfillment information
|
| 56 |
fulfillment_status = Column(String(50), nullable=False, default="pending")
|
| 57 |
-
expected_delivery_date = Column(DateTime, nullable=True)
|
| 58 |
-
actual_delivery_date = Column(DateTime, nullable=True)
|
| 59 |
|
| 60 |
# Invoice information
|
| 61 |
invoice_id = Column(UUID(as_uuid=True), nullable=True)
|
| 62 |
invoice_number = Column(String(50), nullable=True)
|
| 63 |
-
invoice_date = Column(DateTime, nullable=True)
|
| 64 |
invoice_pdf_url = Column(Text, nullable=True)
|
| 65 |
|
| 66 |
# Additional information
|
|
@@ -69,15 +70,15 @@ class SalesOrder(Base):
|
|
| 69 |
|
| 70 |
# Audit fields
|
| 71 |
created_by = Column(String(100), nullable=True)
|
| 72 |
-
created_at = Column(DateTime, nullable=False, default=datetime.utcnow)
|
| 73 |
-
updated_at = Column(DateTime, nullable=False, default=datetime.utcnow, onupdate=datetime.utcnow)
|
| 74 |
approved_by = Column(String(100), nullable=True)
|
| 75 |
-
approved_at = Column(DateTime, nullable=True)
|
| 76 |
|
| 77 |
# Metadata
|
| 78 |
source = Column(String(50), nullable=True, default="ecommerce")
|
| 79 |
channel = Column(String(50), nullable=True, default="web")
|
| 80 |
-
tags = Column(ARRAY(
|
| 81 |
version = Column(Integer, nullable=False, default=1)
|
| 82 |
|
| 83 |
# Relationships
|
|
@@ -91,7 +92,7 @@ class SalesOrderItem(Base):
|
|
| 91 |
__table_args__ = {"schema": "trans"}
|
| 92 |
|
| 93 |
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
| 94 |
-
sales_order_id = Column(UUID(as_uuid=True),
|
| 95 |
|
| 96 |
# Product information
|
| 97 |
sku = Column(String(100), nullable=True)
|
|
@@ -110,7 +111,7 @@ class SalesOrderItem(Base):
|
|
| 110 |
hsn_code = Column(String(20), nullable=True)
|
| 111 |
uom = Column(String(20), nullable=True, default="PCS")
|
| 112 |
batch_no = Column(String(100), nullable=True)
|
| 113 |
-
serials = Column(ARRAY(
|
| 114 |
|
| 115 |
# Service-related fields
|
| 116 |
staff_id = Column(String(100), nullable=True)
|
|
@@ -118,10 +119,10 @@ class SalesOrderItem(Base):
|
|
| 118 |
|
| 119 |
# Metadata
|
| 120 |
remarks = Column(Text, nullable=True)
|
| 121 |
-
created_at = Column(DateTime, nullable=False, default=datetime.utcnow)
|
| 122 |
|
| 123 |
# Relationships
|
| 124 |
-
order = relationship("SalesOrder", back_populates="items")
|
| 125 |
|
| 126 |
|
| 127 |
class SalesOrderAddress(Base):
|
|
@@ -130,7 +131,7 @@ class SalesOrderAddress(Base):
|
|
| 130 |
__table_args__ = {"schema": "trans"}
|
| 131 |
|
| 132 |
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
| 133 |
-
sales_order_id = Column(UUID(as_uuid=True),
|
| 134 |
|
| 135 |
# Address type (shipping, billing)
|
| 136 |
address_type = Column(String(50), nullable=False)
|
|
@@ -145,7 +146,7 @@ class SalesOrderAddress(Base):
|
|
| 145 |
landmark = Column(String(255), nullable=True)
|
| 146 |
|
| 147 |
# Metadata
|
| 148 |
-
created_at = Column(DateTime, nullable=False, default=datetime.utcnow)
|
| 149 |
|
| 150 |
# Relationships
|
| 151 |
-
order = relationship("SalesOrder", back_populates="addresses")
|
|
|
|
| 1 |
"""
|
| 2 |
SQLAlchemy models for sales orders.
|
| 3 |
Maps to trans.sales_orders, trans.sales_order_items, and trans.sales_order_addresses tables.
|
| 4 |
+
Uses UUID for all ID fields.
|
| 5 |
+
No foreign key constraints - relationships managed at application level.
|
| 6 |
"""
|
| 7 |
+
from sqlalchemy import Column, String, Integer, Numeric, DateTime, Text, ARRAY, Date
|
| 8 |
from sqlalchemy.dialects.postgresql import UUID
|
| 9 |
from sqlalchemy.orm import relationship
|
| 10 |
from datetime import datetime
|
|
|
|
| 20 |
|
| 21 |
sales_order_id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
| 22 |
order_number = Column(String(50), unique=True, nullable=False, index=True)
|
|
|
|
| 23 |
merchant_id = Column(UUID(as_uuid=True), nullable=False, index=True)
|
| 24 |
+
order_date = Column(DateTime(timezone=True), nullable=False, default=datetime.utcnow)
|
| 25 |
status = Column(String(50), nullable=False, default="pending")
|
| 26 |
|
| 27 |
# Customer information
|
| 28 |
+
customer_id = Column(UUID(as_uuid=True), nullable=False, index=True)
|
| 29 |
customer_name = Column(String(255), nullable=True)
|
| 30 |
customer_type = Column(String(50), nullable=True, default="retail")
|
| 31 |
customer_phone = Column(String(20), nullable=True)
|
| 32 |
customer_email = Column(String(255), nullable=True)
|
| 33 |
+
customer_gstin = Column(String(15), nullable=True)
|
| 34 |
|
| 35 |
# Financial information
|
| 36 |
subtotal = Column(Numeric(15, 2), nullable=False, default=0.0)
|
|
|
|
| 46 |
payment_type = Column(String(50), nullable=True)
|
| 47 |
payment_status = Column(String(50), nullable=False, default="pending")
|
| 48 |
payment_method = Column(String(50), nullable=True)
|
| 49 |
+
payment_date = Column(DateTime(timezone=True), nullable=True)
|
| 50 |
payment_reference = Column(String(255), nullable=True)
|
| 51 |
amount_paid = Column(Numeric(15, 2), nullable=False, default=0.0)
|
| 52 |
amount_due = Column(Numeric(15, 2), nullable=False, default=0.0)
|
|
|
|
| 55 |
|
| 56 |
# Fulfillment information
|
| 57 |
fulfillment_status = Column(String(50), nullable=False, default="pending")
|
| 58 |
+
expected_delivery_date = Column(DateTime(timezone=True), nullable=True)
|
| 59 |
+
actual_delivery_date = Column(DateTime(timezone=True), nullable=True)
|
| 60 |
|
| 61 |
# Invoice information
|
| 62 |
invoice_id = Column(UUID(as_uuid=True), nullable=True)
|
| 63 |
invoice_number = Column(String(50), nullable=True)
|
| 64 |
+
invoice_date = Column(DateTime(timezone=True), nullable=True)
|
| 65 |
invoice_pdf_url = Column(Text, nullable=True)
|
| 66 |
|
| 67 |
# Additional information
|
|
|
|
| 70 |
|
| 71 |
# Audit fields
|
| 72 |
created_by = Column(String(100), nullable=True)
|
| 73 |
+
created_at = Column(DateTime(timezone=True), nullable=False, default=datetime.utcnow)
|
| 74 |
+
updated_at = Column(DateTime(timezone=True), nullable=False, default=datetime.utcnow, onupdate=datetime.utcnow)
|
| 75 |
approved_by = Column(String(100), nullable=True)
|
| 76 |
+
approved_at = Column(DateTime(timezone=True), nullable=True)
|
| 77 |
|
| 78 |
# Metadata
|
| 79 |
source = Column(String(50), nullable=True, default="ecommerce")
|
| 80 |
channel = Column(String(50), nullable=True, default="web")
|
| 81 |
+
tags = Column(ARRAY(Text), nullable=True)
|
| 82 |
version = Column(Integer, nullable=False, default=1)
|
| 83 |
|
| 84 |
# Relationships
|
|
|
|
| 92 |
__table_args__ = {"schema": "trans"}
|
| 93 |
|
| 94 |
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
| 95 |
+
sales_order_id = Column(UUID(as_uuid=True), nullable=False, index=True)
|
| 96 |
|
| 97 |
# Product information
|
| 98 |
sku = Column(String(100), nullable=True)
|
|
|
|
| 111 |
hsn_code = Column(String(20), nullable=True)
|
| 112 |
uom = Column(String(20), nullable=True, default="PCS")
|
| 113 |
batch_no = Column(String(100), nullable=True)
|
| 114 |
+
serials = Column(ARRAY(Text), nullable=True)
|
| 115 |
|
| 116 |
# Service-related fields
|
| 117 |
staff_id = Column(String(100), nullable=True)
|
|
|
|
| 119 |
|
| 120 |
# Metadata
|
| 121 |
remarks = Column(Text, nullable=True)
|
| 122 |
+
created_at = Column(DateTime(timezone=True), nullable=False, default=datetime.utcnow)
|
| 123 |
|
| 124 |
# Relationships
|
| 125 |
+
order = relationship("SalesOrder", back_populates="items", foreign_keys=[sales_order_id])
|
| 126 |
|
| 127 |
|
| 128 |
class SalesOrderAddress(Base):
|
|
|
|
| 131 |
__table_args__ = {"schema": "trans"}
|
| 132 |
|
| 133 |
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
| 134 |
+
sales_order_id = Column(UUID(as_uuid=True), nullable=False, index=True)
|
| 135 |
|
| 136 |
# Address type (shipping, billing)
|
| 137 |
address_type = Column(String(50), nullable=False)
|
|
|
|
| 146 |
landmark = Column(String(255), nullable=True)
|
| 147 |
|
| 148 |
# Metadata
|
| 149 |
+
created_at = Column(DateTime(timezone=True), nullable=False, default=datetime.utcnow)
|
| 150 |
|
| 151 |
# Relationships
|
| 152 |
+
order = relationship("SalesOrder", back_populates="addresses", foreign_keys=[sales_order_id])
|
|
@@ -106,13 +106,23 @@ class OrderService:
|
|
| 106 |
Create a new order from customer request.
|
| 107 |
|
| 108 |
Args:
|
| 109 |
-
customer_id: Customer ID from JWT token
|
| 110 |
request: Order creation request
|
| 111 |
|
| 112 |
Returns:
|
| 113 |
Result dict with success status and order data
|
| 114 |
"""
|
| 115 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
# Validate and fetch product details for all items
|
| 117 |
items_data = []
|
| 118 |
merchant_ids = set()
|
|
@@ -140,7 +150,17 @@ class OrderService:
|
|
| 140 |
"order": None
|
| 141 |
}
|
| 142 |
|
| 143 |
-
merchant_id
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
|
| 145 |
# Calculate order totals
|
| 146 |
subtotal = Decimal("0.00")
|
|
@@ -174,14 +194,15 @@ class OrderService:
|
|
| 174 |
# Create sales order
|
| 175 |
sales_order = SalesOrder(
|
| 176 |
order_number=order_number,
|
| 177 |
-
|
|
|
|
| 178 |
order_date=datetime.utcnow(),
|
| 179 |
-
status="
|
| 180 |
-
customer_id=customer_id,
|
| 181 |
-
customer_name=request.customer_name,
|
| 182 |
customer_phone=request.customer_phone,
|
| 183 |
customer_email=request.customer_email,
|
| 184 |
-
customer_type="
|
| 185 |
subtotal=float(subtotal),
|
| 186 |
total_discount=0.0,
|
| 187 |
total_tax=float(total_tax),
|
|
@@ -190,14 +211,15 @@ class OrderService:
|
|
| 190 |
cgst=0.0,
|
| 191 |
sgst=0.0,
|
| 192 |
igst=float(total_tax), # Simplified: all tax as IGST
|
| 193 |
-
|
|
|
|
| 194 |
payment_method=request.payment_method,
|
| 195 |
amount_paid=0.0,
|
| 196 |
amount_due=float(grand_total),
|
| 197 |
-
fulfillment_status="pending",
|
| 198 |
notes=request.notes,
|
| 199 |
-
created_by=customer_id,
|
| 200 |
-
source="
|
| 201 |
channel="web"
|
| 202 |
)
|
| 203 |
|
|
@@ -211,7 +233,7 @@ class OrderService:
|
|
| 211 |
order_item = SalesOrderItem(
|
| 212 |
sales_order_id=sales_order.sales_order_id,
|
| 213 |
sku=product["catalogue_code"],
|
| 214 |
-
product_id=product["catalogue_id"],
|
| 215 |
product_name=product["name"],
|
| 216 |
item_type="product",
|
| 217 |
quantity=float(item_data["quantity"]),
|
|
@@ -220,7 +242,7 @@ class OrderService:
|
|
| 220 |
discount_percent=0.0,
|
| 221 |
line_total=float(item_data["line_total"]),
|
| 222 |
hsn_code=product.get("hsn_code"),
|
| 223 |
-
uom=product["unit"]
|
| 224 |
)
|
| 225 |
session.add(order_item)
|
| 226 |
|
|
@@ -305,7 +327,7 @@ class OrderService:
|
|
| 305 |
merchant_id=str(order.merchant_id),
|
| 306 |
order_date=order.order_date,
|
| 307 |
status=order.status,
|
| 308 |
-
customer_id=order.customer_id,
|
| 309 |
customer_name=order.customer_name,
|
| 310 |
customer_phone=order.customer_phone,
|
| 311 |
customer_email=order.customer_email,
|
|
|
|
| 106 |
Create a new order from customer request.
|
| 107 |
|
| 108 |
Args:
|
| 109 |
+
customer_id: Customer ID from JWT token (string, will be converted to UUID)
|
| 110 |
request: Order creation request
|
| 111 |
|
| 112 |
Returns:
|
| 113 |
Result dict with success status and order data
|
| 114 |
"""
|
| 115 |
try:
|
| 116 |
+
# Convert customer_id string to UUID
|
| 117 |
+
from uuid import UUID
|
| 118 |
+
try:
|
| 119 |
+
customer_uuid = UUID(customer_id)
|
| 120 |
+
except (ValueError, AttributeError):
|
| 121 |
+
return {
|
| 122 |
+
"success": False,
|
| 123 |
+
"message": f"Invalid customer_id format: {customer_id}",
|
| 124 |
+
"order": None
|
| 125 |
+
}
|
| 126 |
# Validate and fetch product details for all items
|
| 127 |
items_data = []
|
| 128 |
merchant_ids = set()
|
|
|
|
| 150 |
"order": None
|
| 151 |
}
|
| 152 |
|
| 153 |
+
# Get merchant_id and ensure it's a UUID object
|
| 154 |
+
merchant_id_str = merchant_ids.pop()
|
| 155 |
+
try:
|
| 156 |
+
from uuid import UUID
|
| 157 |
+
merchant_id = UUID(merchant_id_str) if isinstance(merchant_id_str, str) else merchant_id_str
|
| 158 |
+
except (ValueError, AttributeError):
|
| 159 |
+
return {
|
| 160 |
+
"success": False,
|
| 161 |
+
"message": f"Invalid merchant_id format: {merchant_id_str}",
|
| 162 |
+
"order": None
|
| 163 |
+
}
|
| 164 |
|
| 165 |
# Calculate order totals
|
| 166 |
subtotal = Decimal("0.00")
|
|
|
|
| 194 |
# Create sales order
|
| 195 |
sales_order = SalesOrder(
|
| 196 |
order_number=order_number,
|
| 197 |
+
branch_id="", # Empty string for now
|
| 198 |
+
merchant_id=str(merchant_id)[:26], # Truncate if needed
|
| 199 |
order_date=datetime.utcnow(),
|
| 200 |
+
status="draft", # Use valid status from check constraint
|
| 201 |
+
customer_id=customer_id[:26], # Truncate if needed
|
| 202 |
+
customer_name=request.customer_name or "Guest", # Required field
|
| 203 |
customer_phone=request.customer_phone,
|
| 204 |
customer_email=request.customer_email,
|
| 205 |
+
customer_type="b2c", # Valid: b2b or b2c
|
| 206 |
subtotal=float(subtotal),
|
| 207 |
total_discount=0.0,
|
| 208 |
total_tax=float(total_tax),
|
|
|
|
| 211 |
cgst=0.0,
|
| 212 |
sgst=0.0,
|
| 213 |
igst=float(total_tax), # Simplified: all tax as IGST
|
| 214 |
+
payment_type=request.payment_method or "prepaid", # Valid: prepaid, cod, credit, partial
|
| 215 |
+
payment_status="unpaid", # Valid: unpaid, partial, paid, refunded, overdue
|
| 216 |
payment_method=request.payment_method,
|
| 217 |
amount_paid=0.0,
|
| 218 |
amount_due=float(grand_total),
|
| 219 |
+
fulfillment_status="pending", # Valid: pending, allocated, picked, packed, shipped, delivered
|
| 220 |
notes=request.notes,
|
| 221 |
+
created_by=customer_id[:26], # Truncate if needed
|
| 222 |
+
source="web", # Match default
|
| 223 |
channel="web"
|
| 224 |
)
|
| 225 |
|
|
|
|
| 233 |
order_item = SalesOrderItem(
|
| 234 |
sales_order_id=sales_order.sales_order_id,
|
| 235 |
sku=product["catalogue_code"],
|
| 236 |
+
product_id=product["catalogue_id"][:26], # Truncate if needed
|
| 237 |
product_name=product["name"],
|
| 238 |
item_type="product",
|
| 239 |
quantity=float(item_data["quantity"]),
|
|
|
|
| 242 |
discount_percent=0.0,
|
| 243 |
line_total=float(item_data["line_total"]),
|
| 244 |
hsn_code=product.get("hsn_code"),
|
| 245 |
+
uom=product["unit"][:10] if product["unit"] else "PCS" # Truncate to 10 chars
|
| 246 |
)
|
| 247 |
session.add(order_item)
|
| 248 |
|
|
|
|
| 327 |
merchant_id=str(order.merchant_id),
|
| 328 |
order_date=order.order_date,
|
| 329 |
status=order.status,
|
| 330 |
+
customer_id=str(order.customer_id),
|
| 331 |
customer_name=order.customer_name,
|
| 332 |
customer_phone=order.customer_phone,
|
| 333 |
customer_email=order.customer_email,
|
|
@@ -0,0 +1,225 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# E-commerce Order Tables - Database Migrations
|
| 2 |
+
|
| 3 |
+
## Overview
|
| 4 |
+
|
| 5 |
+
This directory contains SQL migration scripts for creating and managing e-commerce order tables in the `trans` schema.
|
| 6 |
+
|
| 7 |
+
## Tables
|
| 8 |
+
|
| 9 |
+
1. **trans.sales_orders** - Main order table
|
| 10 |
+
2. **trans.sales_order_items** - Order line items
|
| 11 |
+
3. **trans.sales_order_addresses** - Shipping and billing addresses
|
| 12 |
+
|
| 13 |
+
## Migration Files
|
| 14 |
+
|
| 15 |
+
### 001_create_ecommerce_order_tables.sql
|
| 16 |
+
Creates all three order tables from scratch with UUID support.
|
| 17 |
+
|
| 18 |
+
**Use when:**
|
| 19 |
+
- Setting up a new database
|
| 20 |
+
- Tables don't exist yet
|
| 21 |
+
- Starting fresh
|
| 22 |
+
|
| 23 |
+
**Features:**
|
| 24 |
+
- UUID primary keys
|
| 25 |
+
- Proper foreign key constraints
|
| 26 |
+
- Indexes for performance
|
| 27 |
+
- Automatic updated_at trigger
|
| 28 |
+
- Comprehensive field set
|
| 29 |
+
|
| 30 |
+
### 002_alter_existing_order_tables_to_uuid.sql
|
| 31 |
+
Drops and recreates existing tables with UUID support.
|
| 32 |
+
|
| 33 |
+
**Use when:**
|
| 34 |
+
- Tables already exist with VARCHAR(26) IDs
|
| 35 |
+
- Need to migrate from ULID to UUID
|
| 36 |
+
- Existing data can be lost
|
| 37 |
+
|
| 38 |
+
**⚠️ WARNING:** This will drop existing tables and data!
|
| 39 |
+
|
| 40 |
+
### 001_rollback_ecommerce_order_tables.sql
|
| 41 |
+
Drops all order tables.
|
| 42 |
+
|
| 43 |
+
**Use when:**
|
| 44 |
+
- Need to completely remove order tables
|
| 45 |
+
- Rolling back a migration
|
| 46 |
+
- Starting over
|
| 47 |
+
|
| 48 |
+
**⚠️ WARNING:** This will permanently delete all order data!
|
| 49 |
+
|
| 50 |
+
## Running Migrations
|
| 51 |
+
|
| 52 |
+
### Option 1: Using Python Script (Recommended)
|
| 53 |
+
|
| 54 |
+
```bash
|
| 55 |
+
# Navigate to db directory
|
| 56 |
+
cd cuatrolabs-ecomm-ms/db
|
| 57 |
+
|
| 58 |
+
# Create new tables
|
| 59 |
+
python run_migration.py 001_create_ecommerce_order_tables.sql
|
| 60 |
+
|
| 61 |
+
# Alter existing tables (with confirmation)
|
| 62 |
+
python run_migration.py 002_alter_existing_order_tables_to_uuid.sql
|
| 63 |
+
|
| 64 |
+
# Rollback (with confirmation)
|
| 65 |
+
python run_migration.py 001_rollback_ecommerce_order_tables.sql
|
| 66 |
+
```
|
| 67 |
+
|
| 68 |
+
### Option 2: Using psql
|
| 69 |
+
|
| 70 |
+
```bash
|
| 71 |
+
# Set connection string
|
| 72 |
+
export DATABASE_URL="postgresql://trans_owner:BookMyService7@ep-sweet-surf-a1qeduoy.ap-southeast-1.aws.neon.tech/cuatrolabs?sslmode=require"
|
| 73 |
+
|
| 74 |
+
# Run migration
|
| 75 |
+
psql $DATABASE_URL -f migrations/001_create_ecommerce_order_tables.sql
|
| 76 |
+
|
| 77 |
+
# Or with explicit connection
|
| 78 |
+
psql -h ep-sweet-surf-a1qeduoy.ap-southeast-1.aws.neon.tech \
|
| 79 |
+
-U trans_owner \
|
| 80 |
+
-d cuatrolabs \
|
| 81 |
+
-f migrations/001_create_ecommerce_order_tables.sql
|
| 82 |
+
```
|
| 83 |
+
|
| 84 |
+
### Option 3: Using DBeaver or pgAdmin
|
| 85 |
+
|
| 86 |
+
1. Connect to your database
|
| 87 |
+
2. Open the SQL script file
|
| 88 |
+
3. Execute the script
|
| 89 |
+
|
| 90 |
+
## Verification
|
| 91 |
+
|
| 92 |
+
After running a migration, verify the tables were created:
|
| 93 |
+
|
| 94 |
+
```sql
|
| 95 |
+
-- List tables
|
| 96 |
+
SELECT schemaname, tablename
|
| 97 |
+
FROM pg_tables
|
| 98 |
+
WHERE schemaname = 'trans'
|
| 99 |
+
AND tablename LIKE 'sales_order%'
|
| 100 |
+
ORDER BY tablename;
|
| 101 |
+
|
| 102 |
+
-- Check table structure
|
| 103 |
+
\d trans.sales_orders
|
| 104 |
+
\d trans.sales_order_items
|
| 105 |
+
\d trans.sales_order_addresses
|
| 106 |
+
|
| 107 |
+
-- Verify UUID columns
|
| 108 |
+
SELECT
|
| 109 |
+
table_name,
|
| 110 |
+
column_name,
|
| 111 |
+
data_type
|
| 112 |
+
FROM information_schema.columns
|
| 113 |
+
WHERE table_schema = 'trans'
|
| 114 |
+
AND table_name IN ('sales_orders', 'sales_order_items', 'sales_order_addresses')
|
| 115 |
+
AND column_name LIKE '%_id'
|
| 116 |
+
ORDER BY table_name, ordinal_position;
|
| 117 |
+
```
|
| 118 |
+
|
| 119 |
+
## Table Schema
|
| 120 |
+
|
| 121 |
+
### trans.sales_orders
|
| 122 |
+
|
| 123 |
+
| Column | Type | Description |
|
| 124 |
+
|--------|------|-------------|
|
| 125 |
+
| sales_order_id | UUID | Primary key |
|
| 126 |
+
| order_number | VARCHAR(50) | Unique order number (e.g., ORD-20260207-0001) |
|
| 127 |
+
| merchant_id | UUID | Merchant ID |
|
| 128 |
+
| customer_id | VARCHAR(100) | Customer ID from auth service |
|
| 129 |
+
| order_date | TIMESTAMP | Order creation date |
|
| 130 |
+
| status | VARCHAR(50) | Order status |
|
| 131 |
+
| subtotal | NUMERIC(15,2) | Subtotal before tax |
|
| 132 |
+
| total_tax | NUMERIC(15,2) | Total tax amount |
|
| 133 |
+
| grand_total | NUMERIC(15,2) | Final total |
|
| 134 |
+
| payment_status | VARCHAR(50) | Payment status |
|
| 135 |
+
| fulfillment_status | VARCHAR(50) | Fulfillment status |
|
| 136 |
+
| ... | ... | Additional fields |
|
| 137 |
+
|
| 138 |
+
### trans.sales_order_items
|
| 139 |
+
|
| 140 |
+
| Column | Type | Description |
|
| 141 |
+
|--------|------|-------------|
|
| 142 |
+
| id | UUID | Primary key |
|
| 143 |
+
| sales_order_id | UUID | Foreign key to sales_orders |
|
| 144 |
+
| product_id | VARCHAR(100) | Product catalogue ID |
|
| 145 |
+
| product_name | VARCHAR(255) | Product name |
|
| 146 |
+
| quantity | NUMERIC(15,3) | Quantity ordered |
|
| 147 |
+
| unit_price | NUMERIC(15,2) | Unit price |
|
| 148 |
+
| line_total | NUMERIC(15,2) | Line total |
|
| 149 |
+
| ... | ... | Additional fields |
|
| 150 |
+
|
| 151 |
+
### trans.sales_order_addresses
|
| 152 |
+
|
| 153 |
+
| Column | Type | Description |
|
| 154 |
+
|--------|------|-------------|
|
| 155 |
+
| id | UUID | Primary key |
|
| 156 |
+
| sales_order_id | UUID | Foreign key to sales_orders |
|
| 157 |
+
| address_type | VARCHAR(50) | shipping or billing |
|
| 158 |
+
| line1 | VARCHAR(255) | Address line 1 |
|
| 159 |
+
| city | VARCHAR(100) | City |
|
| 160 |
+
| state | VARCHAR(100) | State |
|
| 161 |
+
| postal_code | VARCHAR(20) | Postal code |
|
| 162 |
+
| country | VARCHAR(100) | Country |
|
| 163 |
+
|
| 164 |
+
## Environment Variables
|
| 165 |
+
|
| 166 |
+
The Python migration script uses these environment variables:
|
| 167 |
+
|
| 168 |
+
```bash
|
| 169 |
+
DB_HOST=ep-sweet-surf-a1qeduoy.ap-southeast-1.aws.neon.tech
|
| 170 |
+
DB_PORT=5432
|
| 171 |
+
DB_NAME=cuatrolabs
|
| 172 |
+
DB_USER=trans_owner
|
| 173 |
+
DB_PASSWORD=BookMyService7
|
| 174 |
+
DB_SSLMODE=require
|
| 175 |
+
```
|
| 176 |
+
|
| 177 |
+
These can be set in your `.env` file or exported in your shell.
|
| 178 |
+
|
| 179 |
+
## Troubleshooting
|
| 180 |
+
|
| 181 |
+
### Connection Failed
|
| 182 |
+
- Verify database credentials
|
| 183 |
+
- Check network connectivity
|
| 184 |
+
- Ensure SSL mode is correct
|
| 185 |
+
- Verify database exists
|
| 186 |
+
|
| 187 |
+
### Permission Denied
|
| 188 |
+
- Ensure user has CREATE TABLE permissions
|
| 189 |
+
- Check schema permissions
|
| 190 |
+
- Verify user can create functions and triggers
|
| 191 |
+
|
| 192 |
+
### Table Already Exists
|
| 193 |
+
- Use `002_alter_existing_order_tables_to_uuid.sql` to recreate
|
| 194 |
+
- Or use `001_rollback_ecommerce_order_tables.sql` first
|
| 195 |
+
|
| 196 |
+
### Foreign Key Violations
|
| 197 |
+
- Ensure parent tables exist before child tables
|
| 198 |
+
- Check foreign key references are correct
|
| 199 |
+
- Verify CASCADE options are appropriate
|
| 200 |
+
|
| 201 |
+
## Best Practices
|
| 202 |
+
|
| 203 |
+
1. **Backup First**: Always backup your database before running migrations
|
| 204 |
+
2. **Test in Dev**: Test migrations in development environment first
|
| 205 |
+
3. **Review SQL**: Review the SQL scripts before executing
|
| 206 |
+
4. **Verify Results**: Always verify tables were created correctly
|
| 207 |
+
5. **Document Changes**: Keep track of which migrations have been run
|
| 208 |
+
|
| 209 |
+
## Next Steps
|
| 210 |
+
|
| 211 |
+
After running the migration:
|
| 212 |
+
|
| 213 |
+
1. Verify tables were created successfully
|
| 214 |
+
2. Test the order API endpoints
|
| 215 |
+
3. Create sample orders to test functionality
|
| 216 |
+
4. Set up monitoring and logging
|
| 217 |
+
5. Configure backups for order data
|
| 218 |
+
|
| 219 |
+
## Support
|
| 220 |
+
|
| 221 |
+
For issues or questions:
|
| 222 |
+
- Check the verification queries above
|
| 223 |
+
- Review error messages carefully
|
| 224 |
+
- Ensure all prerequisites are met
|
| 225 |
+
- Check database logs for detailed errors
|
|
@@ -0,0 +1,240 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
-- ============================================================================
|
| 2 |
+
-- E-commerce Order Tables Migration
|
| 3 |
+
-- Creates sales_orders, sales_order_items, and sales_order_addresses tables
|
| 4 |
+
-- Uses UUID for all ID fields
|
| 5 |
+
-- ============================================================================
|
| 6 |
+
|
| 7 |
+
-- Ensure trans schema exists
|
| 8 |
+
CREATE SCHEMA IF NOT EXISTS trans;
|
| 9 |
+
|
| 10 |
+
-- Enable UUID extension if not already enabled
|
| 11 |
+
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
| 12 |
+
|
| 13 |
+
-- ============================================================================
|
| 14 |
+
-- Table: trans.sales_orders
|
| 15 |
+
-- Main order table for e-commerce orders
|
| 16 |
+
-- ============================================================================
|
| 17 |
+
|
| 18 |
+
CREATE TABLE IF NOT EXISTS trans.sales_orders (
|
| 19 |
+
-- Primary identification
|
| 20 |
+
sales_order_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
| 21 |
+
order_number VARCHAR(50) UNIQUE NOT NULL,
|
| 22 |
+
|
| 23 |
+
-- Merchant and branch
|
| 24 |
+
merchant_id UUID NOT NULL,
|
| 25 |
+
|
| 26 |
+
-- Order metadata
|
| 27 |
+
order_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
| 28 |
+
status VARCHAR(50) NOT NULL DEFAULT 'pending',
|
| 29 |
+
|
| 30 |
+
-- Customer information
|
| 31 |
+
customer_id UUID NOT NULL,
|
| 32 |
+
customer_name VARCHAR(255),
|
| 33 |
+
customer_type VARCHAR(50) DEFAULT 'retail',
|
| 34 |
+
customer_phone VARCHAR(20),
|
| 35 |
+
customer_email VARCHAR(255),
|
| 36 |
+
customer_gstin VARCHAR(50),
|
| 37 |
+
|
| 38 |
+
-- Financial information
|
| 39 |
+
subtotal NUMERIC(15, 2) NOT NULL DEFAULT 0.00,
|
| 40 |
+
total_discount NUMERIC(15, 2) NOT NULL DEFAULT 0.00,
|
| 41 |
+
total_tax NUMERIC(15, 2) NOT NULL DEFAULT 0.00,
|
| 42 |
+
shipping_charges NUMERIC(15, 2) NOT NULL DEFAULT 0.00,
|
| 43 |
+
grand_total NUMERIC(15, 2) NOT NULL DEFAULT 0.00,
|
| 44 |
+
|
| 45 |
+
-- Tax breakdown
|
| 46 |
+
cgst NUMERIC(15, 2) DEFAULT 0.00,
|
| 47 |
+
sgst NUMERIC(15, 2) DEFAULT 0.00,
|
| 48 |
+
igst NUMERIC(15, 2) DEFAULT 0.00,
|
| 49 |
+
|
| 50 |
+
-- Payment information
|
| 51 |
+
payment_type VARCHAR(50),
|
| 52 |
+
payment_status VARCHAR(50) NOT NULL DEFAULT 'pending',
|
| 53 |
+
payment_method VARCHAR(50),
|
| 54 |
+
payment_date TIMESTAMP WITH TIME ZONE,
|
| 55 |
+
payment_reference VARCHAR(255),
|
| 56 |
+
amount_paid NUMERIC(15, 2) NOT NULL DEFAULT 0.00,
|
| 57 |
+
amount_due NUMERIC(15, 2) NOT NULL DEFAULT 0.00,
|
| 58 |
+
credit_terms VARCHAR(100),
|
| 59 |
+
credit_limit NUMERIC(15, 2),
|
| 60 |
+
|
| 61 |
+
-- Fulfillment information
|
| 62 |
+
fulfillment_status VARCHAR(50) NOT NULL DEFAULT 'pending',
|
| 63 |
+
expected_delivery_date TIMESTAMP WITH TIME ZONE,
|
| 64 |
+
actual_delivery_date TIMESTAMP WITH TIME ZONE,
|
| 65 |
+
|
| 66 |
+
-- Invoice information
|
| 67 |
+
invoice_id UUID,
|
| 68 |
+
invoice_number VARCHAR(50),
|
| 69 |
+
invoice_date TIMESTAMP WITH TIME ZONE,
|
| 70 |
+
invoice_pdf_url TEXT,
|
| 71 |
+
|
| 72 |
+
-- Additional information
|
| 73 |
+
notes TEXT,
|
| 74 |
+
internal_notes TEXT,
|
| 75 |
+
|
| 76 |
+
-- Audit fields
|
| 77 |
+
created_by VARCHAR(100),
|
| 78 |
+
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
| 79 |
+
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
| 80 |
+
approved_by VARCHAR(100),
|
| 81 |
+
approved_at TIMESTAMP WITH TIME ZONE,
|
| 82 |
+
|
| 83 |
+
-- Metadata
|
| 84 |
+
source VARCHAR(50) DEFAULT 'ecommerce',
|
| 85 |
+
channel VARCHAR(50) DEFAULT 'web',
|
| 86 |
+
tags TEXT[],
|
| 87 |
+
version INTEGER NOT NULL DEFAULT 1
|
| 88 |
+
);
|
| 89 |
+
|
| 90 |
+
-- Create indexes for sales_orders
|
| 91 |
+
CREATE INDEX IF NOT EXISTS idx_sales_orders_merchant_id ON trans.sales_orders(merchant_id);
|
| 92 |
+
CREATE INDEX IF NOT EXISTS idx_sales_orders_customer_id ON trans.sales_orders(customer_id);
|
| 93 |
+
CREATE INDEX IF NOT EXISTS idx_sales_orders_order_date ON trans.sales_orders(order_date);
|
| 94 |
+
CREATE INDEX IF NOT EXISTS idx_sales_orders_status ON trans.sales_orders(status);
|
| 95 |
+
CREATE INDEX IF NOT EXISTS idx_sales_orders_payment_status ON trans.sales_orders(payment_status);
|
| 96 |
+
CREATE INDEX IF NOT EXISTS idx_sales_orders_fulfillment_status ON trans.sales_orders(fulfillment_status);
|
| 97 |
+
CREATE INDEX IF NOT EXISTS idx_sales_orders_created_at ON trans.sales_orders(created_at);
|
| 98 |
+
|
| 99 |
+
-- ============================================================================
|
| 100 |
+
-- Table: trans.sales_order_items
|
| 101 |
+
-- Line items for each order
|
| 102 |
+
-- ============================================================================
|
| 103 |
+
|
| 104 |
+
CREATE TABLE IF NOT EXISTS trans.sales_order_items (
|
| 105 |
+
-- Primary identification
|
| 106 |
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
| 107 |
+
sales_order_id UUID NOT NULL,
|
| 108 |
+
|
| 109 |
+
-- Product information
|
| 110 |
+
sku VARCHAR(100),
|
| 111 |
+
product_id VARCHAR(100) NOT NULL,
|
| 112 |
+
product_name VARCHAR(255) NOT NULL,
|
| 113 |
+
item_type VARCHAR(50) DEFAULT 'product',
|
| 114 |
+
|
| 115 |
+
-- Quantity and pricing
|
| 116 |
+
quantity NUMERIC(15, 3) NOT NULL,
|
| 117 |
+
unit_price NUMERIC(15, 2) NOT NULL,
|
| 118 |
+
tax_percent NUMERIC(5, 2) NOT NULL DEFAULT 0.00,
|
| 119 |
+
discount_percent NUMERIC(5, 2) NOT NULL DEFAULT 0.00,
|
| 120 |
+
line_total NUMERIC(15, 2) NOT NULL,
|
| 121 |
+
|
| 122 |
+
-- Additional information
|
| 123 |
+
hsn_code VARCHAR(20),
|
| 124 |
+
uom VARCHAR(20) DEFAULT 'PCS',
|
| 125 |
+
batch_no VARCHAR(100),
|
| 126 |
+
serials TEXT[],
|
| 127 |
+
|
| 128 |
+
-- Service-related fields
|
| 129 |
+
staff_id VARCHAR(100),
|
| 130 |
+
staff_name VARCHAR(255),
|
| 131 |
+
|
| 132 |
+
-- Metadata
|
| 133 |
+
remarks TEXT,
|
| 134 |
+
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
|
| 135 |
+
);
|
| 136 |
+
|
| 137 |
+
-- Create indexes for sales_order_items
|
| 138 |
+
CREATE INDEX IF NOT EXISTS idx_sales_order_items_order_id ON trans.sales_order_items(sales_order_id);
|
| 139 |
+
CREATE INDEX IF NOT EXISTS idx_sales_order_items_product_id ON trans.sales_order_items(product_id);
|
| 140 |
+
|
| 141 |
+
-- ============================================================================
|
| 142 |
+
-- Table: trans.sales_order_addresses
|
| 143 |
+
-- Shipping and billing addresses for orders
|
| 144 |
+
-- ============================================================================
|
| 145 |
+
|
| 146 |
+
CREATE TABLE IF NOT EXISTS trans.sales_order_addresses (
|
| 147 |
+
-- Primary identification
|
| 148 |
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
| 149 |
+
sales_order_id UUID NOT NULL,
|
| 150 |
+
|
| 151 |
+
-- Address type
|
| 152 |
+
address_type VARCHAR(50) NOT NULL,
|
| 153 |
+
|
| 154 |
+
-- Address fields
|
| 155 |
+
line1 VARCHAR(255) NOT NULL,
|
| 156 |
+
line2 VARCHAR(255),
|
| 157 |
+
city VARCHAR(100) NOT NULL,
|
| 158 |
+
state VARCHAR(100) NOT NULL,
|
| 159 |
+
postal_code VARCHAR(20) NOT NULL,
|
| 160 |
+
country VARCHAR(100) NOT NULL DEFAULT 'India',
|
| 161 |
+
landmark VARCHAR(255),
|
| 162 |
+
|
| 163 |
+
-- Metadata
|
| 164 |
+
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
|
| 165 |
+
);
|
| 166 |
+
|
| 167 |
+
-- Create indexes for sales_order_addresses
|
| 168 |
+
CREATE INDEX IF NOT EXISTS idx_sales_order_addresses_order_id ON trans.sales_order_addresses(sales_order_id);
|
| 169 |
+
CREATE INDEX IF NOT EXISTS idx_sales_order_addresses_type ON trans.sales_order_addresses(address_type);
|
| 170 |
+
|
| 171 |
+
-- ============================================================================
|
| 172 |
+
-- Trigger: Update updated_at timestamp automatically
|
| 173 |
+
-- ============================================================================
|
| 174 |
+
|
| 175 |
+
CREATE OR REPLACE FUNCTION trans.update_sales_order_updated_at()
|
| 176 |
+
RETURNS TRIGGER AS $$
|
| 177 |
+
BEGIN
|
| 178 |
+
NEW.updated_at = CURRENT_TIMESTAMP;
|
| 179 |
+
RETURN NEW;
|
| 180 |
+
END;
|
| 181 |
+
$$ LANGUAGE plpgsql;
|
| 182 |
+
|
| 183 |
+
DROP TRIGGER IF EXISTS trigger_update_sales_order_updated_at ON trans.sales_orders;
|
| 184 |
+
|
| 185 |
+
CREATE TRIGGER trigger_update_sales_order_updated_at
|
| 186 |
+
BEFORE UPDATE ON trans.sales_orders
|
| 187 |
+
FOR EACH ROW
|
| 188 |
+
EXECUTE FUNCTION trans.update_sales_order_updated_at();
|
| 189 |
+
|
| 190 |
+
-- ============================================================================
|
| 191 |
+
-- Comments for documentation
|
| 192 |
+
-- ============================================================================
|
| 193 |
+
|
| 194 |
+
COMMENT ON TABLE trans.sales_orders IS 'E-commerce customer orders';
|
| 195 |
+
COMMENT ON TABLE trans.sales_order_items IS 'Line items for e-commerce orders';
|
| 196 |
+
COMMENT ON TABLE trans.sales_order_addresses IS 'Shipping and billing addresses for orders';
|
| 197 |
+
|
| 198 |
+
COMMENT ON COLUMN trans.sales_orders.sales_order_id IS 'Unique order identifier (UUID)';
|
| 199 |
+
COMMENT ON COLUMN trans.sales_orders.order_number IS 'Human-readable order number (e.g., ORD-20260207-0001)';
|
| 200 |
+
COMMENT ON COLUMN trans.sales_orders.customer_id IS 'Customer ID from auth service';
|
| 201 |
+
COMMENT ON COLUMN trans.sales_orders.merchant_id IS 'Merchant ID - all items must be from same merchant';
|
| 202 |
+
COMMENT ON COLUMN trans.sales_orders.source IS 'Order source: ecommerce, pos, etc.';
|
| 203 |
+
COMMENT ON COLUMN trans.sales_orders.channel IS 'Order channel: web, mobile, etc.';
|
| 204 |
+
|
| 205 |
+
-- ============================================================================
|
| 206 |
+
-- Grant permissions (adjust as needed for your setup)
|
| 207 |
+
-- ============================================================================
|
| 208 |
+
|
| 209 |
+
-- Grant permissions to application user (adjust username as needed)
|
| 210 |
+
-- GRANT SELECT, INSERT, UPDATE, DELETE ON trans.sales_orders TO your_app_user;
|
| 211 |
+
-- GRANT SELECT, INSERT, UPDATE, DELETE ON trans.sales_order_items TO your_app_user;
|
| 212 |
+
-- GRANT SELECT, INSERT, UPDATE, DELETE ON trans.sales_order_addresses TO your_app_user;
|
| 213 |
+
|
| 214 |
+
-- ============================================================================
|
| 215 |
+
-- Verification queries
|
| 216 |
+
-- ============================================================================
|
| 217 |
+
|
| 218 |
+
-- Verify tables were created
|
| 219 |
+
SELECT
|
| 220 |
+
schemaname,
|
| 221 |
+
tablename,
|
| 222 |
+
tableowner
|
| 223 |
+
FROM pg_tables
|
| 224 |
+
WHERE schemaname = 'trans'
|
| 225 |
+
AND tablename IN ('sales_orders', 'sales_order_items', 'sales_order_addresses')
|
| 226 |
+
ORDER BY tablename;
|
| 227 |
+
|
| 228 |
+
-- Verify indexes were created
|
| 229 |
+
SELECT
|
| 230 |
+
schemaname,
|
| 231 |
+
tablename,
|
| 232 |
+
indexname
|
| 233 |
+
FROM pg_indexes
|
| 234 |
+
WHERE schemaname = 'trans'
|
| 235 |
+
AND tablename IN ('sales_orders', 'sales_order_items', 'sales_order_addresses')
|
| 236 |
+
ORDER BY tablename, indexname;
|
| 237 |
+
|
| 238 |
+
-- ============================================================================
|
| 239 |
+
-- Migration complete
|
| 240 |
+
-- ============================================================================
|
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
-- ============================================================================
|
| 2 |
+
-- Rollback E-commerce Order Tables Migration
|
| 3 |
+
-- Drops sales_orders, sales_order_items, and sales_order_addresses tables
|
| 4 |
+
-- ============================================================================
|
| 5 |
+
|
| 6 |
+
-- Drop tables in reverse order (child tables first due to foreign keys)
|
| 7 |
+
DROP TABLE IF EXISTS trans.sales_order_addresses CASCADE;
|
| 8 |
+
DROP TABLE IF EXISTS trans.sales_order_items CASCADE;
|
| 9 |
+
DROP TABLE IF EXISTS trans.sales_orders CASCADE;
|
| 10 |
+
|
| 11 |
+
-- Drop trigger function
|
| 12 |
+
DROP FUNCTION IF EXISTS trans.update_sales_order_updated_at() CASCADE;
|
| 13 |
+
|
| 14 |
+
-- Verification
|
| 15 |
+
SELECT
|
| 16 |
+
schemaname,
|
| 17 |
+
tablename
|
| 18 |
+
FROM pg_tables
|
| 19 |
+
WHERE schemaname = 'trans'
|
| 20 |
+
AND tablename IN ('sales_orders', 'sales_order_items', 'sales_order_addresses');
|
| 21 |
+
|
| 22 |
+
-- Should return no rows if rollback was successful
|
|
@@ -0,0 +1,218 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
-- ============================================================================
|
| 2 |
+
-- Alter Existing Order Tables to Support UUID
|
| 3 |
+
-- Modifies existing sales_orders tables to use UUID instead of VARCHAR(26)
|
| 4 |
+
-- WARNING: This will drop and recreate foreign key constraints
|
| 5 |
+
-- ============================================================================
|
| 6 |
+
|
| 7 |
+
-- Enable UUID extension if not already enabled
|
| 8 |
+
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
| 9 |
+
|
| 10 |
+
BEGIN;
|
| 11 |
+
|
| 12 |
+
-- ============================================================================
|
| 13 |
+
-- Step 1: Drop existing tables
|
| 14 |
+
-- ============================================================================
|
| 15 |
+
|
| 16 |
+
-- Backup existing data (optional - uncomment if needed)
|
| 17 |
+
-- CREATE TABLE trans.sales_orders_backup AS SELECT * FROM trans.sales_orders;
|
| 18 |
+
|
| 19 |
+
-- Drop existing table and recreate with UUID
|
| 20 |
+
-- Note: This will lose existing data. Use with caution!
|
| 21 |
+
DROP TABLE IF EXISTS trans.sales_order_addresses CASCADE;
|
| 22 |
+
DROP TABLE IF EXISTS trans.sales_order_items CASCADE;
|
| 23 |
+
DROP TABLE IF EXISTS trans.sales_orders CASCADE;
|
| 24 |
+
|
| 25 |
+
-- Recreate tables with UUID support
|
| 26 |
+
CREATE TABLE trans.sales_orders (
|
| 27 |
+
-- Primary identification
|
| 28 |
+
sales_order_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
| 29 |
+
order_number VARCHAR(50) UNIQUE NOT NULL,
|
| 30 |
+
|
| 31 |
+
-- Merchant and branch
|
| 32 |
+
merchant_id UUID NOT NULL,
|
| 33 |
+
|
| 34 |
+
-- Order metadata
|
| 35 |
+
order_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
| 36 |
+
status VARCHAR(50) NOT NULL DEFAULT 'pending',
|
| 37 |
+
|
| 38 |
+
-- Customer information
|
| 39 |
+
customer_id UUID NOT NULL,
|
| 40 |
+
customer_name VARCHAR(255),
|
| 41 |
+
customer_type VARCHAR(50) DEFAULT 'retail',
|
| 42 |
+
customer_phone VARCHAR(20),
|
| 43 |
+
customer_email VARCHAR(255),
|
| 44 |
+
customer_gstin VARCHAR(50),
|
| 45 |
+
|
| 46 |
+
-- Financial information
|
| 47 |
+
subtotal NUMERIC(15, 2) NOT NULL DEFAULT 0.00,
|
| 48 |
+
total_discount NUMERIC(15, 2) NOT NULL DEFAULT 0.00,
|
| 49 |
+
total_tax NUMERIC(15, 2) NOT NULL DEFAULT 0.00,
|
| 50 |
+
shipping_charges NUMERIC(15, 2) NOT NULL DEFAULT 0.00,
|
| 51 |
+
grand_total NUMERIC(15, 2) NOT NULL DEFAULT 0.00,
|
| 52 |
+
|
| 53 |
+
-- Tax breakdown
|
| 54 |
+
cgst NUMERIC(15, 2) DEFAULT 0.00,
|
| 55 |
+
sgst NUMERIC(15, 2) DEFAULT 0.00,
|
| 56 |
+
igst NUMERIC(15, 2) DEFAULT 0.00,
|
| 57 |
+
|
| 58 |
+
-- Payment information
|
| 59 |
+
payment_type VARCHAR(50),
|
| 60 |
+
payment_status VARCHAR(50) NOT NULL DEFAULT 'pending',
|
| 61 |
+
payment_method VARCHAR(50),
|
| 62 |
+
payment_date TIMESTAMP WITH TIME ZONE,
|
| 63 |
+
payment_reference VARCHAR(255),
|
| 64 |
+
amount_paid NUMERIC(15, 2) NOT NULL DEFAULT 0.00,
|
| 65 |
+
amount_due NUMERIC(15, 2) NOT NULL DEFAULT 0.00,
|
| 66 |
+
credit_terms VARCHAR(100),
|
| 67 |
+
credit_limit NUMERIC(15, 2),
|
| 68 |
+
|
| 69 |
+
-- Fulfillment information
|
| 70 |
+
fulfillment_status VARCHAR(50) NOT NULL DEFAULT 'pending',
|
| 71 |
+
expected_delivery_date TIMESTAMP WITH TIME ZONE,
|
| 72 |
+
actual_delivery_date TIMESTAMP WITH TIME ZONE,
|
| 73 |
+
|
| 74 |
+
-- Invoice information
|
| 75 |
+
invoice_id UUID,
|
| 76 |
+
invoice_number VARCHAR(50),
|
| 77 |
+
invoice_date TIMESTAMP WITH TIME ZONE,
|
| 78 |
+
invoice_pdf_url TEXT,
|
| 79 |
+
|
| 80 |
+
-- Additional information
|
| 81 |
+
notes TEXT,
|
| 82 |
+
internal_notes TEXT,
|
| 83 |
+
|
| 84 |
+
-- Audit fields
|
| 85 |
+
created_by VARCHAR(100),
|
| 86 |
+
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
| 87 |
+
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
| 88 |
+
approved_by VARCHAR(100),
|
| 89 |
+
approved_at TIMESTAMP WITH TIME ZONE,
|
| 90 |
+
|
| 91 |
+
-- Metadata
|
| 92 |
+
source VARCHAR(50) DEFAULT 'ecommerce',
|
| 93 |
+
channel VARCHAR(50) DEFAULT 'web',
|
| 94 |
+
tags TEXT[],
|
| 95 |
+
version INTEGER NOT NULL DEFAULT 1
|
| 96 |
+
);
|
| 97 |
+
|
| 98 |
+
-- ============================================================================
|
| 99 |
+
-- Step 3: Recreate sales_order_items table
|
| 100 |
+
-- ============================================================================
|
| 101 |
+
|
| 102 |
+
CREATE TABLE trans.sales_order_items (
|
| 103 |
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
| 104 |
+
sales_order_id UUID NOT NULL,
|
| 105 |
+
|
| 106 |
+
-- Product information
|
| 107 |
+
sku VARCHAR(100),
|
| 108 |
+
product_id VARCHAR(100) NOT NULL,
|
| 109 |
+
product_name VARCHAR(255) NOT NULL,
|
| 110 |
+
item_type VARCHAR(50) DEFAULT 'product',
|
| 111 |
+
|
| 112 |
+
-- Quantity and pricing
|
| 113 |
+
quantity NUMERIC(15, 3) NOT NULL,
|
| 114 |
+
unit_price NUMERIC(15, 2) NOT NULL,
|
| 115 |
+
tax_percent NUMERIC(5, 2) NOT NULL DEFAULT 0.00,
|
| 116 |
+
discount_percent NUMERIC(5, 2) NOT NULL DEFAULT 0.00,
|
| 117 |
+
line_total NUMERIC(15, 2) NOT NULL,
|
| 118 |
+
|
| 119 |
+
-- Additional information
|
| 120 |
+
hsn_code VARCHAR(20),
|
| 121 |
+
uom VARCHAR(20) DEFAULT 'PCS',
|
| 122 |
+
batch_no VARCHAR(100),
|
| 123 |
+
serials TEXT[],
|
| 124 |
+
|
| 125 |
+
-- Service-related fields
|
| 126 |
+
staff_id VARCHAR(100),
|
| 127 |
+
staff_name VARCHAR(255),
|
| 128 |
+
|
| 129 |
+
-- Metadata
|
| 130 |
+
remarks TEXT,
|
| 131 |
+
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
|
| 132 |
+
);
|
| 133 |
+
|
| 134 |
+
-- ============================================================================
|
| 135 |
+
-- Step 4: Recreate sales_order_addresses table
|
| 136 |
+
-- ============================================================================
|
| 137 |
+
|
| 138 |
+
CREATE TABLE trans.sales_order_addresses (
|
| 139 |
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
| 140 |
+
sales_order_id UUID NOT NULL,
|
| 141 |
+
|
| 142 |
+
-- Address type
|
| 143 |
+
address_type VARCHAR(50) NOT NULL,
|
| 144 |
+
|
| 145 |
+
-- Address fields
|
| 146 |
+
line1 VARCHAR(255) NOT NULL,
|
| 147 |
+
line2 VARCHAR(255),
|
| 148 |
+
city VARCHAR(100) NOT NULL,
|
| 149 |
+
state VARCHAR(100) NOT NULL,
|
| 150 |
+
postal_code VARCHAR(20) NOT NULL,
|
| 151 |
+
country VARCHAR(100) NOT NULL DEFAULT 'India',
|
| 152 |
+
landmark VARCHAR(255),
|
| 153 |
+
|
| 154 |
+
-- Metadata
|
| 155 |
+
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
|
| 156 |
+
);
|
| 157 |
+
|
| 158 |
+
-- ============================================================================
|
| 159 |
+
-- Step 5: Recreate indexes
|
| 160 |
+
-- ============================================================================
|
| 161 |
+
|
| 162 |
+
CREATE INDEX idx_sales_orders_merchant_id ON trans.sales_orders(merchant_id);
|
| 163 |
+
CREATE INDEX idx_sales_orders_customer_id ON trans.sales_orders(customer_id);
|
| 164 |
+
CREATE INDEX idx_sales_orders_order_date ON trans.sales_orders(order_date);
|
| 165 |
+
CREATE INDEX idx_sales_orders_status ON trans.sales_orders(status);
|
| 166 |
+
CREATE INDEX idx_sales_orders_payment_status ON trans.sales_orders(payment_status);
|
| 167 |
+
CREATE INDEX idx_sales_orders_fulfillment_status ON trans.sales_orders(fulfillment_status);
|
| 168 |
+
CREATE INDEX idx_sales_orders_created_at ON trans.sales_orders(created_at);
|
| 169 |
+
|
| 170 |
+
CREATE INDEX idx_sales_order_items_order_id ON trans.sales_order_items(sales_order_id);
|
| 171 |
+
CREATE INDEX idx_sales_order_items_product_id ON trans.sales_order_items(product_id);
|
| 172 |
+
|
| 173 |
+
CREATE INDEX idx_sales_order_addresses_order_id ON trans.sales_order_addresses(sales_order_id);
|
| 174 |
+
CREATE INDEX idx_sales_order_addresses_type ON trans.sales_order_addresses(address_type);
|
| 175 |
+
|
| 176 |
+
-- ============================================================================
|
| 177 |
+
-- Step 6: Recreate trigger
|
| 178 |
+
-- ============================================================================
|
| 179 |
+
|
| 180 |
+
CREATE OR REPLACE FUNCTION trans.update_sales_order_updated_at()
|
| 181 |
+
RETURNS TRIGGER AS $$
|
| 182 |
+
BEGIN
|
| 183 |
+
NEW.updated_at = CURRENT_TIMESTAMP;
|
| 184 |
+
RETURN NEW;
|
| 185 |
+
END;
|
| 186 |
+
$$ LANGUAGE plpgsql;
|
| 187 |
+
|
| 188 |
+
CREATE TRIGGER trigger_update_sales_order_updated_at
|
| 189 |
+
BEFORE UPDATE ON trans.sales_orders
|
| 190 |
+
FOR EACH ROW
|
| 191 |
+
EXECUTE FUNCTION trans.update_sales_order_updated_at();
|
| 192 |
+
|
| 193 |
+
COMMIT;
|
| 194 |
+
|
| 195 |
+
-- ============================================================================
|
| 196 |
+
-- Verification
|
| 197 |
+
-- ============================================================================
|
| 198 |
+
|
| 199 |
+
-- Check table structure
|
| 200 |
+
\d trans.sales_orders
|
| 201 |
+
\d trans.sales_order_items
|
| 202 |
+
\d trans.sales_order_addresses
|
| 203 |
+
|
| 204 |
+
-- Verify UUID columns
|
| 205 |
+
SELECT
|
| 206 |
+
table_name,
|
| 207 |
+
column_name,
|
| 208 |
+
data_type,
|
| 209 |
+
character_maximum_length
|
| 210 |
+
FROM information_schema.columns
|
| 211 |
+
WHERE table_schema = 'trans'
|
| 212 |
+
AND table_name IN ('sales_orders', 'sales_order_items', 'sales_order_addresses')
|
| 213 |
+
AND column_name LIKE '%_id'
|
| 214 |
+
ORDER BY table_name, ordinal_position;
|
| 215 |
+
|
| 216 |
+
-- ============================================================================
|
| 217 |
+
-- Migration complete
|
| 218 |
+
-- ============================================================================
|
|
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Run database migrations for e-commerce order tables.
|
| 3 |
+
"""
|
| 4 |
+
import os
|
| 5 |
+
import sys
|
| 6 |
+
import asyncpg
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
|
| 9 |
+
# Database connection details
|
| 10 |
+
DB_HOST = os.getenv("DB_HOST", "ep-sweet-surf-a1qeduoy.ap-southeast-1.aws.neon.tech")
|
| 11 |
+
DB_PORT = int(os.getenv("DB_PORT", "5432"))
|
| 12 |
+
DB_NAME = os.getenv("DB_NAME", "cuatrolabs")
|
| 13 |
+
DB_USER = os.getenv("DB_USER", "trans_owner")
|
| 14 |
+
DB_PASSWORD = os.getenv("DB_PASSWORD", "BookMyService7")
|
| 15 |
+
DB_SSLMODE = os.getenv("DB_SSLMODE", "require")
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
async def run_migration(migration_file: str):
|
| 19 |
+
"""Run a SQL migration file"""
|
| 20 |
+
|
| 21 |
+
# Read migration file
|
| 22 |
+
migration_path = Path(__file__).parent / "migrations" / migration_file
|
| 23 |
+
|
| 24 |
+
if not migration_path.exists():
|
| 25 |
+
print(f"❌ Migration file not found: {migration_path}")
|
| 26 |
+
return False
|
| 27 |
+
|
| 28 |
+
print(f"\n{'='*70}")
|
| 29 |
+
print(f"Running migration: {migration_file}")
|
| 30 |
+
print(f"{'='*70}\n")
|
| 31 |
+
|
| 32 |
+
with open(migration_path, 'r') as f:
|
| 33 |
+
sql = f.read()
|
| 34 |
+
|
| 35 |
+
# Connect to database
|
| 36 |
+
try:
|
| 37 |
+
conn = await asyncpg.connect(
|
| 38 |
+
host=DB_HOST,
|
| 39 |
+
port=DB_PORT,
|
| 40 |
+
database=DB_NAME,
|
| 41 |
+
user=DB_USER,
|
| 42 |
+
password=DB_PASSWORD,
|
| 43 |
+
ssl='require' if DB_SSLMODE == 'require' else None
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
+
print(f"✅ Connected to database: {DB_NAME}")
|
| 47 |
+
|
| 48 |
+
# Execute migration
|
| 49 |
+
await conn.execute(sql)
|
| 50 |
+
|
| 51 |
+
print(f"✅ Migration executed successfully")
|
| 52 |
+
|
| 53 |
+
# Verify tables
|
| 54 |
+
tables = await conn.fetch("""
|
| 55 |
+
SELECT schemaname, tablename
|
| 56 |
+
FROM pg_tables
|
| 57 |
+
WHERE schemaname = 'trans'
|
| 58 |
+
AND tablename IN ('sales_orders', 'sales_order_items', 'sales_order_addresses')
|
| 59 |
+
ORDER BY tablename
|
| 60 |
+
""")
|
| 61 |
+
|
| 62 |
+
if tables:
|
| 63 |
+
print(f"\n✅ Tables created:")
|
| 64 |
+
for table in tables:
|
| 65 |
+
print(f" - {table['schemaname']}.{table['tablename']}")
|
| 66 |
+
|
| 67 |
+
# Close connection
|
| 68 |
+
await conn.close()
|
| 69 |
+
|
| 70 |
+
return True
|
| 71 |
+
|
| 72 |
+
except Exception as e:
|
| 73 |
+
print(f"\n❌ Migration failed: {str(e)}")
|
| 74 |
+
return False
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
async def main():
|
| 78 |
+
"""Main function"""
|
| 79 |
+
|
| 80 |
+
print("\n" + "="*70)
|
| 81 |
+
print("E-commerce Order Tables Migration")
|
| 82 |
+
print("="*70)
|
| 83 |
+
|
| 84 |
+
if len(sys.argv) < 2:
|
| 85 |
+
print("\nUsage:")
|
| 86 |
+
print(" python run_migration.py <migration_file>")
|
| 87 |
+
print("\nAvailable migrations:")
|
| 88 |
+
print(" 001_create_ecommerce_order_tables.sql - Create new tables")
|
| 89 |
+
print(" 002_alter_existing_order_tables_to_uuid.sql - Alter existing tables")
|
| 90 |
+
print(" 001_rollback_ecommerce_order_tables.sql - Rollback (drop tables)")
|
| 91 |
+
print("\nExample:")
|
| 92 |
+
print(" python run_migration.py 001_create_ecommerce_order_tables.sql")
|
| 93 |
+
return
|
| 94 |
+
|
| 95 |
+
migration_file = sys.argv[1]
|
| 96 |
+
|
| 97 |
+
# Confirm if rollback
|
| 98 |
+
if "rollback" in migration_file.lower():
|
| 99 |
+
print("\n⚠️ WARNING: This will DROP all order tables and data!")
|
| 100 |
+
confirm = input("Type 'yes' to confirm: ")
|
| 101 |
+
if confirm.lower() != 'yes':
|
| 102 |
+
print("❌ Migration cancelled")
|
| 103 |
+
return
|
| 104 |
+
|
| 105 |
+
# Confirm if alter
|
| 106 |
+
if "alter" in migration_file.lower():
|
| 107 |
+
print("\n⚠️ WARNING: This will DROP and RECREATE tables (data will be lost)!")
|
| 108 |
+
confirm = input("Type 'yes' to confirm: ")
|
| 109 |
+
if confirm.lower() != 'yes':
|
| 110 |
+
print("❌ Migration cancelled")
|
| 111 |
+
return
|
| 112 |
+
|
| 113 |
+
# Run migration
|
| 114 |
+
success = await run_migration(migration_file)
|
| 115 |
+
|
| 116 |
+
if success:
|
| 117 |
+
print("\n" + "="*70)
|
| 118 |
+
print("✅ Migration completed successfully!")
|
| 119 |
+
print("="*70 + "\n")
|
| 120 |
+
else:
|
| 121 |
+
print("\n" + "="*70)
|
| 122 |
+
print("❌ Migration failed!")
|
| 123 |
+
print("="*70 + "\n")
|
| 124 |
+
sys.exit(1)
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
if __name__ == "__main__":
|
| 128 |
+
import asyncio
|
| 129 |
+
asyncio.run(main())
|