518 lines
15 KiB
Go
518 lines
15 KiB
Go
|
|
package handlers
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"database/sql"
|
||
|
|
"encoding/json"
|
||
|
|
"fmt"
|
||
|
|
"log"
|
||
|
|
"net/http"
|
||
|
|
"os"
|
||
|
|
"strconv"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"code.springupsoftware.com/cmc/cmc-sales/internal/cmc/db"
|
||
|
|
"code.springupsoftware.com/cmc/cmc-sales/internal/cmc/pdf"
|
||
|
|
"github.com/gorilla/mux"
|
||
|
|
)
|
||
|
|
|
||
|
|
type DocumentHandler struct {
|
||
|
|
queries *db.Queries
|
||
|
|
}
|
||
|
|
|
||
|
|
func NewDocumentHandler(queries *db.Queries) *DocumentHandler {
|
||
|
|
return &DocumentHandler{queries: queries}
|
||
|
|
}
|
||
|
|
|
||
|
|
// List handles GET /api/documents
|
||
|
|
func (h *DocumentHandler) List(w http.ResponseWriter, r *http.Request) {
|
||
|
|
ctx := context.Background()
|
||
|
|
|
||
|
|
// Check for type filter
|
||
|
|
docType := r.URL.Query().Get("type")
|
||
|
|
|
||
|
|
var documents interface{}
|
||
|
|
var err error
|
||
|
|
|
||
|
|
if docType != "" {
|
||
|
|
// Convert string to DocumentsType enum
|
||
|
|
documents, err = h.queries.ListDocumentsByType(ctx, db.DocumentsType(docType))
|
||
|
|
} else {
|
||
|
|
documents, err = h.queries.ListDocuments(ctx)
|
||
|
|
}
|
||
|
|
|
||
|
|
if err != nil {
|
||
|
|
log.Printf("Error fetching documents: %v", err)
|
||
|
|
http.Error(w, "Failed to fetch documents", http.StatusInternalServerError)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
w.Header().Set("Content-Type", "application/json")
|
||
|
|
json.NewEncoder(w).Encode(documents)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Helper functions to convert between pointer and null types
|
||
|
|
func nullInt32FromPtr(p *int32) sql.NullInt32 {
|
||
|
|
if p == nil {
|
||
|
|
return sql.NullInt32{Valid: false}
|
||
|
|
}
|
||
|
|
return sql.NullInt32{Int32: *p, Valid: true}
|
||
|
|
}
|
||
|
|
|
||
|
|
func nullStringFromPtr(p *string) sql.NullString {
|
||
|
|
if p == nil {
|
||
|
|
return sql.NullString{Valid: false}
|
||
|
|
}
|
||
|
|
return sql.NullString{String: *p, Valid: true}
|
||
|
|
}
|
||
|
|
|
||
|
|
func nullTimeFromPtr(p *time.Time) sql.NullTime {
|
||
|
|
if p == nil {
|
||
|
|
return sql.NullTime{Valid: false}
|
||
|
|
}
|
||
|
|
return sql.NullTime{Time: *p, Valid: true}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Get handles GET /api/documents/{id}
|
||
|
|
func (h *DocumentHandler) Get(w http.ResponseWriter, r *http.Request) {
|
||
|
|
vars := mux.Vars(r)
|
||
|
|
idStr := vars["id"]
|
||
|
|
|
||
|
|
id, err := strconv.ParseInt(idStr, 10, 64)
|
||
|
|
if err != nil {
|
||
|
|
http.Error(w, "Invalid document ID", http.StatusBadRequest)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
ctx := context.Background()
|
||
|
|
document, err := h.queries.GetDocumentByID(ctx, int32(id))
|
||
|
|
if err != nil {
|
||
|
|
log.Printf("Error fetching document %d: %v", id, err)
|
||
|
|
http.Error(w, "Document not found", http.StatusNotFound)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
w.Header().Set("Content-Type", "application/json")
|
||
|
|
json.NewEncoder(w).Encode(document)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Create handles POST /api/documents
|
||
|
|
func (h *DocumentHandler) Create(w http.ResponseWriter, r *http.Request) {
|
||
|
|
var req struct {
|
||
|
|
Type string `json:"type"`
|
||
|
|
UserID int32 `json:"user_id"`
|
||
|
|
DocPageCount int32 `json:"doc_page_count"`
|
||
|
|
CMCReference string `json:"cmc_reference"`
|
||
|
|
PDFFilename string `json:"pdf_filename"`
|
||
|
|
PDFCreatedAt string `json:"pdf_created_at"`
|
||
|
|
PDFCreatedByUserID int32 `json:"pdf_created_by_user_id"`
|
||
|
|
ShippingDetails *string `json:"shipping_details,omitempty"`
|
||
|
|
Revision *int32 `json:"revision,omitempty"`
|
||
|
|
BillTo *string `json:"bill_to,omitempty"`
|
||
|
|
ShipTo *string `json:"ship_to,omitempty"`
|
||
|
|
EmailSentAt string `json:"email_sent_at"`
|
||
|
|
EmailSentByUserID int32 `json:"email_sent_by_user_id"`
|
||
|
|
}
|
||
|
|
|
||
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||
|
|
http.Error(w, "Invalid JSON", http.StatusBadRequest)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
// Validate required fields
|
||
|
|
if req.Type == "" {
|
||
|
|
http.Error(w, "Type is required", http.StatusBadRequest)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
if req.UserID == 0 {
|
||
|
|
http.Error(w, "User ID is required", http.StatusBadRequest)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
if req.CMCReference == "" {
|
||
|
|
http.Error(w, "CMC Reference is required", http.StatusBadRequest)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
ctx := context.Background()
|
||
|
|
|
||
|
|
// Parse timestamps
|
||
|
|
pdfCreatedAt, err := time.Parse("2006-01-02 15:04:05", req.PDFCreatedAt)
|
||
|
|
if err != nil {
|
||
|
|
http.Error(w, "Invalid pdf_created_at format", http.StatusBadRequest)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
emailSentAt, err := time.Parse("2006-01-02 15:04:05", req.EmailSentAt)
|
||
|
|
if err != nil {
|
||
|
|
http.Error(w, "Invalid email_sent_at format", http.StatusBadRequest)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
revision := int32(0)
|
||
|
|
if req.Revision != nil {
|
||
|
|
revision = *req.Revision
|
||
|
|
}
|
||
|
|
|
||
|
|
params := db.CreateDocumentParams{
|
||
|
|
Type: db.DocumentsType(req.Type),
|
||
|
|
UserID: req.UserID,
|
||
|
|
DocPageCount: req.DocPageCount,
|
||
|
|
CmcReference: req.CMCReference,
|
||
|
|
PdfFilename: req.PDFFilename,
|
||
|
|
PdfCreatedAt: pdfCreatedAt,
|
||
|
|
PdfCreatedByUserID: req.PDFCreatedByUserID,
|
||
|
|
ShippingDetails: nullStringFromPtr(req.ShippingDetails),
|
||
|
|
Revision: revision,
|
||
|
|
BillTo: nullStringFromPtr(req.BillTo),
|
||
|
|
ShipTo: nullStringFromPtr(req.ShipTo),
|
||
|
|
EmailSentAt: emailSentAt,
|
||
|
|
EmailSentByUserID: req.EmailSentByUserID,
|
||
|
|
}
|
||
|
|
|
||
|
|
result, err := h.queries.CreateDocument(ctx, params)
|
||
|
|
if err != nil {
|
||
|
|
log.Printf("Error creating document: %v", err)
|
||
|
|
http.Error(w, "Failed to create document", http.StatusInternalServerError)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
id, _ := result.LastInsertId()
|
||
|
|
|
||
|
|
w.Header().Set("Content-Type", "application/json")
|
||
|
|
w.WriteHeader(http.StatusCreated)
|
||
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
||
|
|
"id": id,
|
||
|
|
"message": "Document created successfully",
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
// Update handles PUT /api/documents/{id}
|
||
|
|
func (h *DocumentHandler) Update(w http.ResponseWriter, r *http.Request) {
|
||
|
|
vars := mux.Vars(r)
|
||
|
|
idStr := vars["id"]
|
||
|
|
|
||
|
|
id, err := strconv.ParseInt(idStr, 10, 64)
|
||
|
|
if err != nil {
|
||
|
|
http.Error(w, "Invalid document ID", http.StatusBadRequest)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
var req struct {
|
||
|
|
Type string `json:"type"`
|
||
|
|
UserID int32 `json:"user_id"`
|
||
|
|
DocPageCount int32 `json:"doc_page_count"`
|
||
|
|
CMCReference string `json:"cmc_reference"`
|
||
|
|
PDFFilename string `json:"pdf_filename"`
|
||
|
|
PDFCreatedAt string `json:"pdf_created_at"`
|
||
|
|
PDFCreatedByUserID int32 `json:"pdf_created_by_user_id"`
|
||
|
|
ShippingDetails *string `json:"shipping_details,omitempty"`
|
||
|
|
Revision *int32 `json:"revision,omitempty"`
|
||
|
|
BillTo *string `json:"bill_to,omitempty"`
|
||
|
|
ShipTo *string `json:"ship_to,omitempty"`
|
||
|
|
EmailSentAt string `json:"email_sent_at"`
|
||
|
|
EmailSentByUserID int32 `json:"email_sent_by_user_id"`
|
||
|
|
}
|
||
|
|
|
||
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||
|
|
http.Error(w, "Invalid JSON", http.StatusBadRequest)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
ctx := context.Background()
|
||
|
|
|
||
|
|
// Parse timestamps
|
||
|
|
pdfCreatedAt, err := time.Parse("2006-01-02 15:04:05", req.PDFCreatedAt)
|
||
|
|
if err != nil {
|
||
|
|
http.Error(w, "Invalid pdf_created_at format", http.StatusBadRequest)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
emailSentAt, err := time.Parse("2006-01-02 15:04:05", req.EmailSentAt)
|
||
|
|
if err != nil {
|
||
|
|
http.Error(w, "Invalid email_sent_at format", http.StatusBadRequest)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
revision := int32(0)
|
||
|
|
if req.Revision != nil {
|
||
|
|
revision = *req.Revision
|
||
|
|
}
|
||
|
|
|
||
|
|
params := db.UpdateDocumentParams{
|
||
|
|
Type: db.DocumentsType(req.Type),
|
||
|
|
UserID: req.UserID,
|
||
|
|
DocPageCount: req.DocPageCount,
|
||
|
|
CmcReference: req.CMCReference,
|
||
|
|
PdfFilename: req.PDFFilename,
|
||
|
|
PdfCreatedAt: pdfCreatedAt,
|
||
|
|
PdfCreatedByUserID: req.PDFCreatedByUserID,
|
||
|
|
ShippingDetails: nullStringFromPtr(req.ShippingDetails),
|
||
|
|
Revision: revision,
|
||
|
|
BillTo: nullStringFromPtr(req.BillTo),
|
||
|
|
ShipTo: nullStringFromPtr(req.ShipTo),
|
||
|
|
EmailSentAt: emailSentAt,
|
||
|
|
EmailSentByUserID: req.EmailSentByUserID,
|
||
|
|
ID: int32(id),
|
||
|
|
}
|
||
|
|
|
||
|
|
err = h.queries.UpdateDocument(ctx, params)
|
||
|
|
if err != nil {
|
||
|
|
log.Printf("Error updating document %d: %v", id, err)
|
||
|
|
http.Error(w, "Failed to update document", http.StatusInternalServerError)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
w.Header().Set("Content-Type", "application/json")
|
||
|
|
json.NewEncoder(w).Encode(map[string]string{
|
||
|
|
"message": "Document updated successfully",
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
// Archive handles PUT /api/documents/{id}/archive
|
||
|
|
func (h *DocumentHandler) Archive(w http.ResponseWriter, r *http.Request) {
|
||
|
|
vars := mux.Vars(r)
|
||
|
|
idStr := vars["id"]
|
||
|
|
|
||
|
|
id, err := strconv.ParseInt(idStr, 10, 64)
|
||
|
|
if err != nil {
|
||
|
|
http.Error(w, "Invalid document ID", http.StatusBadRequest)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
ctx := context.Background()
|
||
|
|
err = h.queries.ArchiveDocument(ctx, int32(id))
|
||
|
|
if err != nil {
|
||
|
|
log.Printf("Error archiving document %d: %v", id, err)
|
||
|
|
http.Error(w, "Failed to archive document", http.StatusInternalServerError)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
w.Header().Set("Content-Type", "application/json")
|
||
|
|
json.NewEncoder(w).Encode(map[string]string{
|
||
|
|
"message": "Document archived successfully",
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
// Unarchive handles PUT /api/documents/{id}/unarchive
|
||
|
|
func (h *DocumentHandler) Unarchive(w http.ResponseWriter, r *http.Request) {
|
||
|
|
vars := mux.Vars(r)
|
||
|
|
idStr := vars["id"]
|
||
|
|
|
||
|
|
id, err := strconv.ParseInt(idStr, 10, 64)
|
||
|
|
if err != nil {
|
||
|
|
http.Error(w, "Invalid document ID", http.StatusBadRequest)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
ctx := context.Background()
|
||
|
|
err = h.queries.UnarchiveDocument(ctx, db.UnarchiveDocumentParams{
|
||
|
|
Column1: int32(id),
|
||
|
|
Column2: int32(id),
|
||
|
|
})
|
||
|
|
if err != nil {
|
||
|
|
log.Printf("Error unarchiving document %d: %v", id, err)
|
||
|
|
http.Error(w, "Failed to unarchive document", http.StatusInternalServerError)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
w.Header().Set("Content-Type", "application/json")
|
||
|
|
json.NewEncoder(w).Encode(map[string]string{
|
||
|
|
"message": "Document unarchived successfully",
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
// Search handles GET /api/documents/search?q=query
|
||
|
|
func (h *DocumentHandler) Search(w http.ResponseWriter, r *http.Request) {
|
||
|
|
query := r.URL.Query().Get("q")
|
||
|
|
if query == "" {
|
||
|
|
http.Error(w, "Search query is required", http.StatusBadRequest)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
ctx := context.Background()
|
||
|
|
searchPattern := "%" + query + "%"
|
||
|
|
documents, err := h.queries.SearchDocuments(ctx, db.SearchDocumentsParams{
|
||
|
|
PdfFilename: searchPattern,
|
||
|
|
CmcReference: searchPattern,
|
||
|
|
Name: searchPattern,
|
||
|
|
Title: searchPattern,
|
||
|
|
Username: searchPattern,
|
||
|
|
})
|
||
|
|
if err != nil {
|
||
|
|
log.Printf("Error searching documents: %v", err)
|
||
|
|
http.Error(w, "Failed to search documents", http.StatusInternalServerError)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
w.Header().Set("Content-Type", "application/json")
|
||
|
|
json.NewEncoder(w).Encode(documents)
|
||
|
|
}
|
||
|
|
|
||
|
|
// GeneratePDF handles GET /documents/pdf/{id}
|
||
|
|
func (h *DocumentHandler) GeneratePDF(w http.ResponseWriter, r *http.Request) {
|
||
|
|
vars := mux.Vars(r)
|
||
|
|
idStr := vars["id"]
|
||
|
|
|
||
|
|
id, err := strconv.ParseInt(idStr, 10, 64)
|
||
|
|
if err != nil {
|
||
|
|
http.Error(w, "Invalid document ID", http.StatusBadRequest)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
ctx := context.Background()
|
||
|
|
|
||
|
|
// Get document
|
||
|
|
document, err := h.queries.GetDocumentByID(ctx, int32(id))
|
||
|
|
if err != nil {
|
||
|
|
log.Printf("Error fetching document %d: %v", id, err)
|
||
|
|
http.Error(w, "Document not found", http.StatusNotFound)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
// Get output directory from environment or use default
|
||
|
|
outputDir := os.Getenv("PDF_OUTPUT_DIR")
|
||
|
|
if outputDir == "" {
|
||
|
|
outputDir = "webroot/pdf"
|
||
|
|
}
|
||
|
|
|
||
|
|
// Ensure output directory exists
|
||
|
|
if err := os.MkdirAll(outputDir, 0755); err != nil {
|
||
|
|
log.Printf("Error creating PDF directory: %v", err)
|
||
|
|
http.Error(w, "Failed to create PDF directory", http.StatusInternalServerError)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
var filename string
|
||
|
|
|
||
|
|
// Generate PDF based on document type
|
||
|
|
log.Printf("Generating PDF for document %d, type: %s, outputDir: %s", id, document.Type, outputDir)
|
||
|
|
switch document.Type {
|
||
|
|
case db.DocumentsTypeQuote:
|
||
|
|
// Get quote-specific data
|
||
|
|
// TODO: Get quote data from database
|
||
|
|
|
||
|
|
// Get enquiry data
|
||
|
|
// TODO: Get enquiry from quote
|
||
|
|
|
||
|
|
// Get customer data
|
||
|
|
// TODO: Get customer from enquiry
|
||
|
|
|
||
|
|
// Get user data
|
||
|
|
user, err := h.queries.GetUser(ctx, document.UserID)
|
||
|
|
if err != nil {
|
||
|
|
log.Printf("Error fetching user: %v", err)
|
||
|
|
http.Error(w, "Failed to fetch user data", http.StatusInternalServerError)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
// Get line items
|
||
|
|
lineItems, err := h.queries.GetLineItemsTable(ctx, int32(id))
|
||
|
|
if err != nil {
|
||
|
|
log.Printf("Error fetching line items: %v", err)
|
||
|
|
http.Error(w, "Failed to fetch line items", http.StatusInternalServerError)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
// For now, create a simplified quote PDF
|
||
|
|
data := &pdf.QuotePDFData{
|
||
|
|
Document: &document,
|
||
|
|
User: &user,
|
||
|
|
LineItems: lineItems,
|
||
|
|
CurrencySymbol: "$", // Default to AUD
|
||
|
|
ShowGST: true, // Default to showing GST
|
||
|
|
}
|
||
|
|
|
||
|
|
// Generate PDF
|
||
|
|
log.Printf("Calling GenerateQuotePDF with outputDir: %s", outputDir)
|
||
|
|
filename, err = pdf.GenerateQuotePDF(data, outputDir)
|
||
|
|
if err != nil {
|
||
|
|
log.Printf("Error generating quote PDF: %v", err)
|
||
|
|
http.Error(w, "Failed to generate PDF", http.StatusInternalServerError)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
log.Printf("Successfully generated PDF: %s", filename)
|
||
|
|
|
||
|
|
case db.DocumentsTypeInvoice:
|
||
|
|
// Get invoice-specific data
|
||
|
|
// TODO: Implement invoice PDF generation
|
||
|
|
http.Error(w, "Invoice PDF generation not yet implemented", http.StatusNotImplemented)
|
||
|
|
return
|
||
|
|
|
||
|
|
case db.DocumentsTypePurchaseOrder:
|
||
|
|
// Get purchase order data
|
||
|
|
po, err := h.queries.GetPurchaseOrderByDocumentID(ctx, int32(id))
|
||
|
|
if err != nil {
|
||
|
|
log.Printf("Error fetching purchase order: %v", err)
|
||
|
|
http.Error(w, "Failed to fetch purchase order data", http.StatusInternalServerError)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
// Get principle data
|
||
|
|
principle, err := h.queries.GetPrinciple(ctx, po.PrincipleID)
|
||
|
|
if err != nil {
|
||
|
|
log.Printf("Error fetching principle: %v", err)
|
||
|
|
http.Error(w, "Failed to fetch principle data", http.StatusInternalServerError)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
// Get line items
|
||
|
|
lineItems, err := h.queries.GetLineItemsTable(ctx, int32(id))
|
||
|
|
if err != nil {
|
||
|
|
log.Printf("Error fetching line items: %v", err)
|
||
|
|
http.Error(w, "Failed to fetch line items", http.StatusInternalServerError)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
// Create purchase order PDF data
|
||
|
|
data := &pdf.PurchaseOrderPDFData{
|
||
|
|
Document: &document,
|
||
|
|
PurchaseOrder: &po,
|
||
|
|
Principle: &principle,
|
||
|
|
LineItems: lineItems,
|
||
|
|
CurrencySymbol: "$", // Default to AUD
|
||
|
|
ShowGST: true, // Default to showing GST for Australian principles
|
||
|
|
}
|
||
|
|
|
||
|
|
// Generate PDF
|
||
|
|
filename, err = pdf.GeneratePurchaseOrderPDF(data, outputDir)
|
||
|
|
if err != nil {
|
||
|
|
log.Printf("Error generating purchase order PDF: %v", err)
|
||
|
|
http.Error(w, "Failed to generate PDF", http.StatusInternalServerError)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
default:
|
||
|
|
http.Error(w, "Unsupported document type for PDF generation", http.StatusBadRequest)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
// Update document with PDF filename and timestamp
|
||
|
|
now := time.Now()
|
||
|
|
err = h.queries.UpdateDocumentPDFInfo(ctx, db.UpdateDocumentPDFInfoParams{
|
||
|
|
PdfFilename: filename,
|
||
|
|
PdfCreatedAt: now,
|
||
|
|
PdfCreatedByUserID: 1, // TODO: Get current user ID
|
||
|
|
ID: int32(id),
|
||
|
|
})
|
||
|
|
if err != nil {
|
||
|
|
log.Printf("Error updating document PDF info: %v", err)
|
||
|
|
// Don't fail the request, PDF was generated successfully
|
||
|
|
}
|
||
|
|
|
||
|
|
// Return success response with redirect to document view
|
||
|
|
w.Header().Set("Content-Type", "text/html")
|
||
|
|
fmt.Fprintf(w, `
|
||
|
|
<html>
|
||
|
|
<head>
|
||
|
|
<script type="text/javascript">
|
||
|
|
window.location.replace("/documents/view/%d");
|
||
|
|
</script>
|
||
|
|
</head>
|
||
|
|
<body>
|
||
|
|
<p>PDF generated successfully. <a href="/documents/view/%d">Click here</a> if you are not redirected.</p>
|
||
|
|
</body>
|
||
|
|
</html>
|
||
|
|
`, id, id)
|
||
|
|
}
|