Преглед на файлове

feat: auto-generate uplatnica PDF for montenegrin payment on shipping

unknown преди 1 седмица
родител
ревизия
b78daaf58d
променени са 6 файла, в които са добавени 117 реда и са изтрити 2 реда
  1. 4 0
      backend/config.py
  2. 16 0
      backend/routers/orders.py
  3. 1 0
      backend/schema.sql
  4. 83 0
      backend/services/uplatnica_generator.py
  5. 7 2
      src/pages/Admin.vue
  6. 6 0
      src/pages/Orders.vue

+ 4 - 0
backend/config.py

@@ -28,3 +28,7 @@ PREVIEW_DIR = os.path.join(UPLOAD_DIR, "previews")
 for d in [UPLOAD_DIR, PREVIEW_DIR]:
     if not os.path.exists(d):
         os.makedirs(d)
+
+# Payment Config
+ZIRO_RACUN = "510-00000000000-00"
+COMPANY_NAME = "RADIONICA 3D"

+ 16 - 0
backend/routers/orders.py

@@ -176,6 +176,22 @@ async def update_order_admin(
                 order_info[0], 
                 data.send_notification
             )
+            
+            # Generate Uplatnica PDF Document on moving to "shipped" step
+            if data.status == 'shipped' and not order_info[0].get('invoice_path'):
+                from services.uplatnica_generator import generate_uplatnica
+                o = order_info[0]
+                payer_name = f"{o['first_name']} {o.get('last_name', '')}".strip()
+                addr = o.get('shipping_address', '')
+                price = float(o['total_price'] if o.get('total_price') is not None else o.get('estimated_price', 0))
+                
+                try:
+                    pdf_path = generate_uplatnica(order_id, payer_name, addr, price)
+                    update_fields.append("invoice_path = %s")
+                    params.append(pdf_path)
+                except Exception as e:
+                    print(f"Failed to generate invoice PDF: {e}")
+
         update_fields.append("status = %s")
         params.append(data.status)
     if data.total_price is not None:

+ 1 - 0
backend/schema.sql

@@ -72,6 +72,7 @@ CREATE TABLE IF NOT EXISTS orders (
     allow_portfolio BOOLEAN DEFAULT FALSE,
     quantity INT DEFAULT 1,
     notes TEXT,
+    invoice_path TEXT NULL,
     created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
     updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
     FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL

+ 83 - 0
backend/services/uplatnica_generator.py

@@ -0,0 +1,83 @@
+import os
+from fpdf import FPDF
+import config
+
+def generate_uplatnica(order_id: int, payer_name: str, payer_address: str, amount: float) -> str:
+    """
+    Generate a simple PDF 'Nalog za uplatu' (Uplatnica) for Montenegro.
+    Returns the absolute path to the generated PDF.
+    """
+    pdf = FPDF(orientation='L', unit='mm', format=(100, 210))
+    pdf.add_page()
+    
+    # We will just use the default Helvetica or Arial
+    pdf.set_font("helvetica", 'B', 12)
+    
+    # Draw simple border
+    pdf.rect(5, 5, 200, 90)
+    
+    # Title
+    pdf.set_y(10)
+    pdf.set_font("helvetica", 'B', 14)
+    pdf.cell(0, 10, "NALOG ZA UPLATU", align="C")
+    
+    pdf.set_font("helvetica", '', 10)
+    
+    # Payer Info (Uplatilac)
+    pdf.set_xy(10, 25)
+    pdf.cell(40, 5, "UPLATILAC:")
+    pdf.set_xy(10, 30)
+    pdf.set_font("helvetica", 'B', 10)
+    pdf.multi_cell(80, 5, f"{payer_name}\n{payer_address}")
+    pdf.set_font("helvetica", '', 10)
+    
+    # Purpose (Svrha uplate)
+    pdf.set_xy(10, 50)
+    pdf.cell(40, 5, "SVRHA UPLATE:")
+    pdf.set_xy(10, 55)
+    pdf.set_font("helvetica", 'B', 10)
+    pdf.multi_cell(80, 5, f"Placanje za 3D stampu - Narudzba #{order_id}")
+    pdf.set_font("helvetica", '', 10)
+    
+    # Recipient (Primalac)
+    pdf.set_xy(10, 75)
+    pdf.cell(40, 5, "PRIMALAC:")
+    pdf.set_xy(10, 80)
+    pdf.set_font("helvetica", 'B', 10)
+    pdf.multi_cell(80, 5, config.COMPANY_NAME)
+    pdf.set_font("helvetica", '', 10)
+    
+    # Right column
+    # Amount
+    pdf.set_xy(100, 25)
+    pdf.cell(40, 5, "IZNOS (EUR):")
+    pdf.set_xy(140, 23)
+    pdf.set_font("helvetica", 'B', 14)
+    pdf.cell(40, 8, f"={amount:.2f}", border=1, align="C")
+    pdf.set_font("helvetica", '', 10)
+    
+    # Account
+    pdf.set_xy(100, 40)
+    pdf.cell(40, 5, "RACUN PRIMAOCA:")
+    pdf.set_xy(140, 38)
+    pdf.set_font("helvetica", 'B', 12)
+    pdf.cell(60, 8, config.ZIRO_RACUN, border=1, align="C")
+    pdf.set_font("helvetica", '', 10)
+    
+    # Reference
+    pdf.set_xy(100, 55)
+    pdf.cell(40, 5, "POZIV NA BROJ:")
+    pdf.set_xy(140, 53)
+    pdf.set_font("helvetica", 'B', 12)
+    pdf.cell(60, 8, str(order_id), border=1, align="C")
+    
+    # Save file
+    pdf_dir = os.path.join(config.UPLOAD_DIR, "invoices")
+    if not os.path.exists(pdf_dir):
+        os.makedirs(pdf_dir)
+        
+    filename = f"uplatnica_order_{order_id}.pdf"
+    filepath = os.path.join(pdf_dir, filename)
+    pdf.output(filepath)
+    
+    return os.path.join("uploads", "invoices", filename).replace("\\", "/")

+ 7 - 2
src/pages/Admin.vue

@@ -189,8 +189,13 @@
                   <button @click="editingPrice = { id: order.id, price: order.total_price?.toString() ?? '' }" class="text-[10px] text-primary hover:underline font-bold">Edit</button>
                 </div>
                 <div class="text-2xl font-display font-bold">{{ order.total_price || "---" }} <span class="text-xs">EUR</span></div>
-                <button @click="toggleAdminChat(order.id)" class="mt-4 w-full flex items-center justify-center gap-2 py-2 rounded-xl border border-border/50 hover:border-primary/30 text-sm font-bold transition-all"
-                  :class="adminChatId === order.id ? 'bg-primary text-primary-foreground' : 'bg-background/50 text-muted-foreground hover:text-foreground'">
+                
+                <a v-if="order.invoice_path" :href="`http://localhost:8000/${order.invoice_path}`" target="_blank"
+                   class="mt-4 w-full flex items-center justify-center gap-2 py-2 rounded-xl bg-primary/10 hover:bg-primary/20 text-primary border border-primary/20 font-bold transition-all text-sm">
+                  <FileText class="w-4 h-4" /> Print Uplatnica
+                </a>
+
+                <button @click="toggleAdminChat(order.id)" :class="['w-full flex items-center justify-center gap-2 py-2 rounded-xl border border-border/50 hover:border-primary/30 text-sm font-bold transition-all', order.invoice_path ? 'mt-2' : 'mt-4', adminChatId === order.id ? 'bg-primary text-primary-foreground' : 'bg-background/50 text-muted-foreground hover:text-foreground']">
                   <MessageCircle class="w-4 h-4" />{{ t("chat.open") }}
                 </button>
               </div>

+ 6 - 0
src/pages/Orders.vue

@@ -67,6 +67,12 @@
                   <span class="font-display font-bold text-lg">{{ order.total_price ? `${order.total_price} €` : "Pending..." }}</span>
                 </div>
                 <div class="flex items-center gap-2">
+                  <a v-if="order.invoice_path" :href="`http://localhost:8000/${order.invoice_path}`" target="_blank"
+                    class="p-2 bg-primary/10 hover:bg-primary/20 text-primary rounded-lg transition-colors flex items-center gap-1"
+                    title="Download Uplatnica">
+                    <FileText class="w-4 h-4" />
+                    <span class="text-xs font-bold px-1 hidden sm:inline">Uplatnica</span>
+                  </a>
                   <a v-if="order.model_link && !order.model_link.startsWith('javascript:')"
                     :href="order.model_link" target="_blank" rel="noopener noreferrer"
                     class="p-2 hover:bg-secondary rounded-lg transition-colors">