Progress on the go-rewrite

This commit is contained in:
Karl Cordes 2025-07-07 07:34:12 +10:00
parent 4f54a93c62
commit 28b331737f
12 changed files with 509 additions and 749 deletions

View file

@ -167,6 +167,9 @@ func main() {
w.Write([]byte(`{"status":"ok"}`)) w.Write([]byte(`{"status":"ok"}`))
}).Methods("GET") }).Methods("GET")
// Recent activity endpoint
r.HandleFunc("/api/recent-activity", documentHandler.GetRecentActivity).Methods("GET")
// Page routes // Page routes
r.HandleFunc("/", pageHandler.Home).Methods("GET") r.HandleFunc("/", pageHandler.Home).Methods("GET")

View file

@ -11,39 +11,40 @@ import (
"time" "time"
) )
const archiveDocument = `-- name: ArchiveDocument :exec const countDocuments = `-- name: CountDocuments :one
DELETE FROM documents SELECT COUNT(*) FROM documents
WHERE id = ?
` `
func (q *Queries) ArchiveDocument(ctx context.Context, id int32) error { func (q *Queries) CountDocuments(ctx context.Context) (int64, error) {
_, err := q.db.ExecContext(ctx, archiveDocument, id) row := q.db.QueryRowContext(ctx, countDocuments)
return err var count int64
err := row.Scan(&count)
return count, err
}
const countDocumentsByType = `-- name: CountDocumentsByType :one
SELECT COUNT(*) FROM documents WHERE type = ?
`
func (q *Queries) CountDocumentsByType(ctx context.Context, type_ DocumentsType) (int64, error) {
row := q.db.QueryRowContext(ctx, countDocumentsByType, type_)
var count int64
err := row.Scan(&count)
return count, err
} }
const createDocument = `-- name: CreateDocument :execresult const createDocument = `-- name: CreateDocument :execresult
INSERT INTO documents ( INSERT INTO documents (
type, type, created, user_id, doc_page_count, cmc_reference,
created, pdf_filename, pdf_created_at, pdf_created_by_user_id,
user_id, shipping_details, revision, bill_to, ship_to,
doc_page_count, email_sent_at, email_sent_by_user_id
cmc_reference, ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
pdf_filename,
pdf_created_at,
pdf_created_by_user_id,
shipping_details,
revision,
bill_to,
ship_to,
email_sent_at,
email_sent_by_user_id
) VALUES (
?, NOW(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
)
` `
type CreateDocumentParams struct { type CreateDocumentParams struct {
Type DocumentsType `json:"type"` Type DocumentsType `json:"type"`
Created time.Time `json:"created"`
UserID int32 `json:"user_id"` UserID int32 `json:"user_id"`
DocPageCount int32 `json:"doc_page_count"` DocPageCount int32 `json:"doc_page_count"`
CmcReference string `json:"cmc_reference"` CmcReference string `json:"cmc_reference"`
@ -61,6 +62,7 @@ type CreateDocumentParams struct {
func (q *Queries) CreateDocument(ctx context.Context, arg CreateDocumentParams) (sql.Result, error) { func (q *Queries) CreateDocument(ctx context.Context, arg CreateDocumentParams) (sql.Result, error) {
return q.db.ExecContext(ctx, createDocument, return q.db.ExecContext(ctx, createDocument,
arg.Type, arg.Type,
arg.Created,
arg.UserID, arg.UserID,
arg.DocPageCount, arg.DocPageCount,
arg.CmcReference, arg.CmcReference,
@ -76,219 +78,230 @@ func (q *Queries) CreateDocument(ctx context.Context, arg CreateDocumentParams)
) )
} }
const getDocumentByID = `-- name: GetDocumentByID :one const deleteDocument = `-- name: DeleteDocument :exec
SELECT DELETE FROM documents WHERE id = ?
d.id,
d.type,
d.created,
d.user_id,
d.doc_page_count,
d.pdf_filename,
d.pdf_created_at,
d.pdf_created_by_user_id,
d.cmc_reference,
d.revision,
d.shipping_details,
d.bill_to,
d.ship_to,
d.email_sent_at,
d.email_sent_by_user_id,
u.first_name as user_first_name,
u.last_name as user_last_name,
u.username as user_username,
pdf_creator.first_name as pdf_creator_first_name,
pdf_creator.last_name as pdf_creator_last_name,
pdf_creator.username as pdf_creator_username,
COALESCE(ec.name, ic.name, '') as customer_name,
e.title as enquiry_title
FROM documents d
LEFT JOIN users u ON d.user_id = u.id
LEFT JOIN users pdf_creator ON d.pdf_created_by_user_id = pdf_creator.id
LEFT JOIN enquiries e ON d.type IN ('quote', 'orderAck') AND d.cmc_reference = e.title
LEFT JOIN customers ec ON e.customer_id = ec.id
LEFT JOIN invoices i ON d.type = 'invoice' AND d.cmc_reference = i.title
LEFT JOIN customers ic ON i.customer_id = ic.id
WHERE d.id = ?
` `
type GetDocumentByIDRow struct { func (q *Queries) DeleteDocument(ctx context.Context, id int32) error {
ID int32 `json:"id"` _, err := q.db.ExecContext(ctx, deleteDocument, id)
Type DocumentsType `json:"type"` return err
Created time.Time `json:"created"`
UserID int32 `json:"user_id"`
DocPageCount int32 `json:"doc_page_count"`
PdfFilename string `json:"pdf_filename"`
PdfCreatedAt time.Time `json:"pdf_created_at"`
PdfCreatedByUserID int32 `json:"pdf_created_by_user_id"`
CmcReference string `json:"cmc_reference"`
Revision int32 `json:"revision"`
ShippingDetails sql.NullString `json:"shipping_details"`
BillTo sql.NullString `json:"bill_to"`
ShipTo sql.NullString `json:"ship_to"`
EmailSentAt time.Time `json:"email_sent_at"`
EmailSentByUserID int32 `json:"email_sent_by_user_id"`
UserFirstName sql.NullString `json:"user_first_name"`
UserLastName sql.NullString `json:"user_last_name"`
UserUsername sql.NullString `json:"user_username"`
PdfCreatorFirstName sql.NullString `json:"pdf_creator_first_name"`
PdfCreatorLastName sql.NullString `json:"pdf_creator_last_name"`
PdfCreatorUsername sql.NullString `json:"pdf_creator_username"`
CustomerName string `json:"customer_name"`
EnquiryTitle sql.NullString `json:"enquiry_title"`
} }
func (q *Queries) GetDocumentByID(ctx context.Context, id int32) (GetDocumentByIDRow, error) { const getDocument = `-- name: GetDocument :one
row := q.db.QueryRowContext(ctx, getDocumentByID, id) SELECT id, type, created, user_id, doc_page_count, cmc_reference, pdf_filename, pdf_created_at, pdf_created_by_user_id, shipping_details, revision, bill_to, ship_to, email_sent_at, email_sent_by_user_id FROM documents WHERE id = ?
var i GetDocumentByIDRow `
func (q *Queries) GetDocument(ctx context.Context, id int32) (Document, error) {
row := q.db.QueryRowContext(ctx, getDocument, id)
var i Document
err := row.Scan( err := row.Scan(
&i.ID, &i.ID,
&i.Type, &i.Type,
&i.Created, &i.Created,
&i.UserID, &i.UserID,
&i.DocPageCount, &i.DocPageCount,
&i.CmcReference,
&i.PdfFilename, &i.PdfFilename,
&i.PdfCreatedAt, &i.PdfCreatedAt,
&i.PdfCreatedByUserID, &i.PdfCreatedByUserID,
&i.CmcReference,
&i.Revision,
&i.ShippingDetails, &i.ShippingDetails,
&i.Revision,
&i.BillTo, &i.BillTo,
&i.ShipTo, &i.ShipTo,
&i.EmailSentAt, &i.EmailSentAt,
&i.EmailSentByUserID, &i.EmailSentByUserID,
&i.UserFirstName,
&i.UserLastName,
&i.UserUsername,
&i.PdfCreatorFirstName,
&i.PdfCreatorLastName,
&i.PdfCreatorUsername,
&i.CustomerName,
&i.EnquiryTitle,
) )
return i, err return i, err
} }
const getPurchaseOrderByDocumentID = `-- name: GetPurchaseOrderByDocumentID :one const getDocumentWithUser = `-- name: GetDocumentWithUser :one
SELECT
po.id,
po.title,
po.principle_id,
po.principle_reference,
po.issue_date,
po.ordered_from,
po.dispatch_by,
po.deliver_to,
po.shipping_instructions
FROM purchase_orders po
JOIN documents d ON d.cmc_reference = po.title
WHERE d.id = ? AND d.type = 'purchaseOrder'
`
type GetPurchaseOrderByDocumentIDRow struct {
ID int32 `json:"id"`
Title string `json:"title"`
PrincipleID int32 `json:"principle_id"`
PrincipleReference string `json:"principle_reference"`
IssueDate time.Time `json:"issue_date"`
OrderedFrom string `json:"ordered_from"`
DispatchBy string `json:"dispatch_by"`
DeliverTo string `json:"deliver_to"`
ShippingInstructions string `json:"shipping_instructions"`
}
func (q *Queries) GetPurchaseOrderByDocumentID(ctx context.Context, id int32) (GetPurchaseOrderByDocumentIDRow, error) {
row := q.db.QueryRowContext(ctx, getPurchaseOrderByDocumentID, id)
var i GetPurchaseOrderByDocumentIDRow
err := row.Scan(
&i.ID,
&i.Title,
&i.PrincipleID,
&i.PrincipleReference,
&i.IssueDate,
&i.OrderedFrom,
&i.DispatchBy,
&i.DeliverTo,
&i.ShippingInstructions,
)
return i, err
}
const listDocuments = `-- name: ListDocuments :many
SELECT SELECT
d.id, d.id,
d.type, d.type,
d.created, d.created,
d.user_id, d.user_id,
d.doc_page_count, d.doc_page_count,
d.cmc_reference,
d.pdf_filename, d.pdf_filename,
d.pdf_created_at, d.pdf_created_at,
d.pdf_created_by_user_id, d.pdf_created_by_user_id,
d.cmc_reference, d.shipping_details,
d.revision, d.revision,
d.bill_to,
d.ship_to,
d.email_sent_at, d.email_sent_at,
d.email_sent_by_user_id, d.email_sent_by_user_id,
u.username as user_username,
u.first_name as user_first_name, u.first_name as user_first_name,
u.last_name as user_last_name, u.last_name as user_last_name,
u.username as user_username, u.email as user_email,
pdf_creator.first_name as pdf_creator_first_name, pu.username as pdf_creator_username,
pdf_creator.last_name as pdf_creator_last_name, pu.first_name as pdf_creator_first_name,
pdf_creator.username as pdf_creator_username pu.last_name as pdf_creator_last_name,
pu.email as pdf_creator_email
FROM documents d FROM documents d
LEFT JOIN users u ON d.user_id = u.id LEFT JOIN users u ON d.user_id = u.id
LEFT JOIN users pdf_creator ON d.pdf_created_by_user_id = pdf_creator.id LEFT JOIN users pu ON d.pdf_created_by_user_id = pu.id
ORDER BY d.id DESC WHERE d.id = ?
LIMIT 1000
` `
type ListDocumentsRow struct { type GetDocumentWithUserRow struct {
ID int32 `json:"id"` ID int32 `json:"id"`
Type DocumentsType `json:"type"` Type DocumentsType `json:"type"`
Created time.Time `json:"created"` Created time.Time `json:"created"`
UserID int32 `json:"user_id"` UserID int32 `json:"user_id"`
DocPageCount int32 `json:"doc_page_count"` DocPageCount int32 `json:"doc_page_count"`
CmcReference string `json:"cmc_reference"`
PdfFilename string `json:"pdf_filename"` PdfFilename string `json:"pdf_filename"`
PdfCreatedAt time.Time `json:"pdf_created_at"` PdfCreatedAt time.Time `json:"pdf_created_at"`
PdfCreatedByUserID int32 `json:"pdf_created_by_user_id"` PdfCreatedByUserID int32 `json:"pdf_created_by_user_id"`
CmcReference string `json:"cmc_reference"` ShippingDetails sql.NullString `json:"shipping_details"`
Revision int32 `json:"revision"` Revision int32 `json:"revision"`
BillTo sql.NullString `json:"bill_to"`
ShipTo sql.NullString `json:"ship_to"`
EmailSentAt time.Time `json:"email_sent_at"` EmailSentAt time.Time `json:"email_sent_at"`
EmailSentByUserID int32 `json:"email_sent_by_user_id"` EmailSentByUserID int32 `json:"email_sent_by_user_id"`
UserUsername sql.NullString `json:"user_username"`
UserFirstName sql.NullString `json:"user_first_name"` UserFirstName sql.NullString `json:"user_first_name"`
UserLastName sql.NullString `json:"user_last_name"` UserLastName sql.NullString `json:"user_last_name"`
UserUsername sql.NullString `json:"user_username"` UserEmail sql.NullString `json:"user_email"`
PdfCreatorUsername sql.NullString `json:"pdf_creator_username"`
PdfCreatorFirstName sql.NullString `json:"pdf_creator_first_name"` PdfCreatorFirstName sql.NullString `json:"pdf_creator_first_name"`
PdfCreatorLastName sql.NullString `json:"pdf_creator_last_name"` PdfCreatorLastName sql.NullString `json:"pdf_creator_last_name"`
PdfCreatorUsername sql.NullString `json:"pdf_creator_username"` PdfCreatorEmail sql.NullString `json:"pdf_creator_email"`
} }
func (q *Queries) ListDocuments(ctx context.Context) ([]ListDocumentsRow, error) { func (q *Queries) GetDocumentWithUser(ctx context.Context, id int32) (GetDocumentWithUserRow, error) {
rows, err := q.db.QueryContext(ctx, listDocuments) row := q.db.QueryRowContext(ctx, getDocumentWithUser, id)
var i GetDocumentWithUserRow
err := row.Scan(
&i.ID,
&i.Type,
&i.Created,
&i.UserID,
&i.DocPageCount,
&i.CmcReference,
&i.PdfFilename,
&i.PdfCreatedAt,
&i.PdfCreatedByUserID,
&i.ShippingDetails,
&i.Revision,
&i.BillTo,
&i.ShipTo,
&i.EmailSentAt,
&i.EmailSentByUserID,
&i.UserUsername,
&i.UserFirstName,
&i.UserLastName,
&i.UserEmail,
&i.PdfCreatorUsername,
&i.PdfCreatorFirstName,
&i.PdfCreatorLastName,
&i.PdfCreatorEmail,
)
return i, err
}
const getRecentDocuments = `-- name: GetRecentDocuments :many
SELECT
d.id,
d.type,
d.created,
d.cmc_reference,
d.pdf_filename,
d.revision,
u.username as created_by_username,
CASE
WHEN d.type = 'quote' THEN CONCAT('Quote ', d.cmc_reference)
WHEN d.type = 'invoice' THEN CONCAT('Invoice ', d.cmc_reference)
WHEN d.type = 'purchaseOrder' THEN CONCAT('Purchase Order ', d.cmc_reference)
WHEN d.type = 'orderAck' THEN CONCAT('Order Ack ', d.cmc_reference)
WHEN d.type = 'packingList' THEN CONCAT('Packing List ', d.cmc_reference)
ELSE CONCAT(d.type, ' ', d.cmc_reference)
END as display_name
FROM documents d
LEFT JOIN users u ON d.user_id = u.id
ORDER BY d.created DESC
LIMIT ?
`
type GetRecentDocumentsRow struct {
ID int32 `json:"id"`
Type DocumentsType `json:"type"`
Created time.Time `json:"created"`
CmcReference string `json:"cmc_reference"`
PdfFilename string `json:"pdf_filename"`
Revision int32 `json:"revision"`
CreatedByUsername sql.NullString `json:"created_by_username"`
DisplayName interface{} `json:"display_name"`
}
func (q *Queries) GetRecentDocuments(ctx context.Context, limit int32) ([]GetRecentDocumentsRow, error) {
rows, err := q.db.QueryContext(ctx, getRecentDocuments, limit)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer rows.Close() defer rows.Close()
items := []ListDocumentsRow{} items := []GetRecentDocumentsRow{}
for rows.Next() { for rows.Next() {
var i ListDocumentsRow var i GetRecentDocumentsRow
if err := rows.Scan(
&i.ID,
&i.Type,
&i.Created,
&i.CmcReference,
&i.PdfFilename,
&i.Revision,
&i.CreatedByUsername,
&i.DisplayName,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listDocuments = `-- name: ListDocuments :many
SELECT id, type, created, user_id, doc_page_count, cmc_reference, pdf_filename, pdf_created_at, pdf_created_by_user_id, shipping_details, revision, bill_to, ship_to, email_sent_at, email_sent_by_user_id FROM documents ORDER BY created DESC LIMIT ? OFFSET ?
`
type ListDocumentsParams struct {
Limit int32 `json:"limit"`
Offset int32 `json:"offset"`
}
func (q *Queries) ListDocuments(ctx context.Context, arg ListDocumentsParams) ([]Document, error) {
rows, err := q.db.QueryContext(ctx, listDocuments, arg.Limit, arg.Offset)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Document{}
for rows.Next() {
var i Document
if err := rows.Scan( if err := rows.Scan(
&i.ID, &i.ID,
&i.Type, &i.Type,
&i.Created, &i.Created,
&i.UserID, &i.UserID,
&i.DocPageCount, &i.DocPageCount,
&i.CmcReference,
&i.PdfFilename, &i.PdfFilename,
&i.PdfCreatedAt, &i.PdfCreatedAt,
&i.PdfCreatedByUserID, &i.PdfCreatedByUserID,
&i.CmcReference, &i.ShippingDetails,
&i.Revision, &i.Revision,
&i.BillTo,
&i.ShipTo,
&i.EmailSentAt, &i.EmailSentAt,
&i.EmailSentByUserID, &i.EmailSentByUserID,
&i.UserFirstName,
&i.UserLastName,
&i.UserUsername,
&i.PdfCreatorFirstName,
&i.PdfCreatorLastName,
&i.PdfCreatorUsername,
); err != nil { ); err != nil {
return nil, err return nil, err
} }
@ -304,92 +317,40 @@ func (q *Queries) ListDocuments(ctx context.Context) ([]ListDocumentsRow, error)
} }
const listDocumentsByType = `-- name: ListDocumentsByType :many const listDocumentsByType = `-- name: ListDocumentsByType :many
SELECT SELECT id, type, created, user_id, doc_page_count, cmc_reference, pdf_filename, pdf_created_at, pdf_created_by_user_id, shipping_details, revision, bill_to, ship_to, email_sent_at, email_sent_by_user_id FROM documents WHERE type = ? ORDER BY created DESC LIMIT ? OFFSET ?
d.id,
d.type,
d.created,
d.user_id,
d.doc_page_count,
d.pdf_filename,
d.pdf_created_at,
d.pdf_created_by_user_id,
d.cmc_reference,
d.revision,
d.email_sent_at,
d.email_sent_by_user_id,
u.first_name as user_first_name,
u.last_name as user_last_name,
u.username as user_username,
pdf_creator.first_name as pdf_creator_first_name,
pdf_creator.last_name as pdf_creator_last_name,
pdf_creator.username as pdf_creator_username,
COALESCE(ec.name, ic.name, '') as customer_name,
e.title as enquiry_title
FROM documents d
LEFT JOIN users u ON d.user_id = u.id
LEFT JOIN users pdf_creator ON d.pdf_created_by_user_id = pdf_creator.id
LEFT JOIN enquiries e ON d.type IN ('quote', 'orderAck') AND d.cmc_reference = e.title
LEFT JOIN customers ec ON e.customer_id = ec.id
LEFT JOIN invoices i ON d.type = 'invoice' AND d.cmc_reference = i.title
LEFT JOIN customers ic ON i.customer_id = ic.id
WHERE d.type = ?
ORDER BY d.id DESC
LIMIT 1000
` `
type ListDocumentsByTypeRow struct { type ListDocumentsByTypeParams struct {
ID int32 `json:"id"` Type DocumentsType `json:"type"`
Type DocumentsType `json:"type"` Limit int32 `json:"limit"`
Created time.Time `json:"created"` Offset int32 `json:"offset"`
UserID int32 `json:"user_id"`
DocPageCount int32 `json:"doc_page_count"`
PdfFilename string `json:"pdf_filename"`
PdfCreatedAt time.Time `json:"pdf_created_at"`
PdfCreatedByUserID int32 `json:"pdf_created_by_user_id"`
CmcReference string `json:"cmc_reference"`
Revision int32 `json:"revision"`
EmailSentAt time.Time `json:"email_sent_at"`
EmailSentByUserID int32 `json:"email_sent_by_user_id"`
UserFirstName sql.NullString `json:"user_first_name"`
UserLastName sql.NullString `json:"user_last_name"`
UserUsername sql.NullString `json:"user_username"`
PdfCreatorFirstName sql.NullString `json:"pdf_creator_first_name"`
PdfCreatorLastName sql.NullString `json:"pdf_creator_last_name"`
PdfCreatorUsername sql.NullString `json:"pdf_creator_username"`
CustomerName string `json:"customer_name"`
EnquiryTitle sql.NullString `json:"enquiry_title"`
} }
func (q *Queries) ListDocumentsByType(ctx context.Context, type_ DocumentsType) ([]ListDocumentsByTypeRow, error) { func (q *Queries) ListDocumentsByType(ctx context.Context, arg ListDocumentsByTypeParams) ([]Document, error) {
rows, err := q.db.QueryContext(ctx, listDocumentsByType, type_) rows, err := q.db.QueryContext(ctx, listDocumentsByType, arg.Type, arg.Limit, arg.Offset)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer rows.Close() defer rows.Close()
items := []ListDocumentsByTypeRow{} items := []Document{}
for rows.Next() { for rows.Next() {
var i ListDocumentsByTypeRow var i Document
if err := rows.Scan( if err := rows.Scan(
&i.ID, &i.ID,
&i.Type, &i.Type,
&i.Created, &i.Created,
&i.UserID, &i.UserID,
&i.DocPageCount, &i.DocPageCount,
&i.CmcReference,
&i.PdfFilename, &i.PdfFilename,
&i.PdfCreatedAt, &i.PdfCreatedAt,
&i.PdfCreatedByUserID, &i.PdfCreatedByUserID,
&i.CmcReference, &i.ShippingDetails,
&i.Revision, &i.Revision,
&i.BillTo,
&i.ShipTo,
&i.EmailSentAt, &i.EmailSentAt,
&i.EmailSentByUserID, &i.EmailSentByUserID,
&i.UserFirstName,
&i.UserLastName,
&i.UserUsername,
&i.PdfCreatorFirstName,
&i.PdfCreatorLastName,
&i.PdfCreatorUsername,
&i.CustomerName,
&i.EnquiryTitle,
); err != nil { ); err != nil {
return nil, err return nil, err
} }
@ -404,159 +365,12 @@ func (q *Queries) ListDocumentsByType(ctx context.Context, type_ DocumentsType)
return items, nil return items, nil
} }
const searchDocuments = `-- name: SearchDocuments :many
SELECT
d.id,
d.type,
d.created,
d.user_id,
d.doc_page_count,
d.pdf_filename,
d.pdf_created_at,
d.pdf_created_by_user_id,
d.cmc_reference,
d.revision,
d.email_sent_at,
d.email_sent_by_user_id,
u.first_name as user_first_name,
u.last_name as user_last_name,
u.username as user_username,
pdf_creator.first_name as pdf_creator_first_name,
pdf_creator.last_name as pdf_creator_last_name,
pdf_creator.username as pdf_creator_username,
COALESCE(ec.name, ic.name, '') as customer_name,
e.title as enquiry_title
FROM documents d
LEFT JOIN users u ON d.user_id = u.id
LEFT JOIN users pdf_creator ON d.pdf_created_by_user_id = pdf_creator.id
LEFT JOIN enquiries e ON d.type IN ('quote', 'orderAck') AND d.cmc_reference = e.title
LEFT JOIN customers ec ON e.customer_id = ec.id
LEFT JOIN invoices i ON d.type = 'invoice' AND d.cmc_reference = i.title
LEFT JOIN customers ic ON i.customer_id = ic.id
WHERE (
d.pdf_filename LIKE ? OR
d.cmc_reference LIKE ? OR
COALESCE(ec.name, ic.name) LIKE ? OR
e.title LIKE ? OR
u.username LIKE ?
)
ORDER BY d.id DESC
LIMIT 1000
`
type SearchDocumentsParams struct {
PdfFilename string `json:"pdf_filename"`
CmcReference string `json:"cmc_reference"`
Name string `json:"name"`
Title string `json:"title"`
Username string `json:"username"`
}
type SearchDocumentsRow struct {
ID int32 `json:"id"`
Type DocumentsType `json:"type"`
Created time.Time `json:"created"`
UserID int32 `json:"user_id"`
DocPageCount int32 `json:"doc_page_count"`
PdfFilename string `json:"pdf_filename"`
PdfCreatedAt time.Time `json:"pdf_created_at"`
PdfCreatedByUserID int32 `json:"pdf_created_by_user_id"`
CmcReference string `json:"cmc_reference"`
Revision int32 `json:"revision"`
EmailSentAt time.Time `json:"email_sent_at"`
EmailSentByUserID int32 `json:"email_sent_by_user_id"`
UserFirstName sql.NullString `json:"user_first_name"`
UserLastName sql.NullString `json:"user_last_name"`
UserUsername sql.NullString `json:"user_username"`
PdfCreatorFirstName sql.NullString `json:"pdf_creator_first_name"`
PdfCreatorLastName sql.NullString `json:"pdf_creator_last_name"`
PdfCreatorUsername sql.NullString `json:"pdf_creator_username"`
CustomerName string `json:"customer_name"`
EnquiryTitle sql.NullString `json:"enquiry_title"`
}
func (q *Queries) SearchDocuments(ctx context.Context, arg SearchDocumentsParams) ([]SearchDocumentsRow, error) {
rows, err := q.db.QueryContext(ctx, searchDocuments,
arg.PdfFilename,
arg.CmcReference,
arg.Name,
arg.Title,
arg.Username,
)
if err != nil {
return nil, err
}
defer rows.Close()
items := []SearchDocumentsRow{}
for rows.Next() {
var i SearchDocumentsRow
if err := rows.Scan(
&i.ID,
&i.Type,
&i.Created,
&i.UserID,
&i.DocPageCount,
&i.PdfFilename,
&i.PdfCreatedAt,
&i.PdfCreatedByUserID,
&i.CmcReference,
&i.Revision,
&i.EmailSentAt,
&i.EmailSentByUserID,
&i.UserFirstName,
&i.UserLastName,
&i.UserUsername,
&i.PdfCreatorFirstName,
&i.PdfCreatorLastName,
&i.PdfCreatorUsername,
&i.CustomerName,
&i.EnquiryTitle,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const unarchiveDocument = `-- name: UnarchiveDocument :exec
SELECT 1 WHERE ? = ?
`
type UnarchiveDocumentParams struct {
Column1 interface{} `json:"column_1"`
Column2 interface{} `json:"column_2"`
}
// Note: Unarchiving not supported as documents table doesn't have an archived column
// This is a no-op for compatibility
func (q *Queries) UnarchiveDocument(ctx context.Context, arg UnarchiveDocumentParams) error {
_, err := q.db.ExecContext(ctx, unarchiveDocument, arg.Column1, arg.Column2)
return err
}
const updateDocument = `-- name: UpdateDocument :exec const updateDocument = `-- name: UpdateDocument :exec
UPDATE documents UPDATE documents SET
SET type = ?, user_id = ?, doc_page_count = ?, cmc_reference = ?,
type = ?, pdf_filename = ?, pdf_created_at = ?, pdf_created_by_user_id = ?,
user_id = ?, shipping_details = ?, revision = ?, bill_to = ?, ship_to = ?,
doc_page_count = ?, email_sent_at = ?, email_sent_by_user_id = ?
cmc_reference = ?,
pdf_filename = ?,
pdf_created_at = ?,
pdf_created_by_user_id = ?,
shipping_details = ?,
revision = ?,
bill_to = ?,
ship_to = ?,
email_sent_at = ?,
email_sent_by_user_id = ?
WHERE id = ? WHERE id = ?
` `
@ -596,29 +410,3 @@ func (q *Queries) UpdateDocument(ctx context.Context, arg UpdateDocumentParams)
) )
return err return err
} }
const updateDocumentPDFInfo = `-- name: UpdateDocumentPDFInfo :exec
UPDATE documents
SET
pdf_filename = ?,
pdf_created_at = ?,
pdf_created_by_user_id = ?
WHERE id = ?
`
type UpdateDocumentPDFInfoParams struct {
PdfFilename string `json:"pdf_filename"`
PdfCreatedAt time.Time `json:"pdf_created_at"`
PdfCreatedByUserID int32 `json:"pdf_created_by_user_id"`
ID int32 `json:"id"`
}
func (q *Queries) UpdateDocumentPDFInfo(ctx context.Context, arg UpdateDocumentPDFInfoParams) error {
_, err := q.db.ExecContext(ctx, updateDocumentPDFInfo,
arg.PdfFilename,
arg.PdfCreatedAt,
arg.PdfCreatedByUserID,
arg.ID,
)
return err
}

View file

@ -103,6 +103,37 @@ func (q *Queries) GetPurchaseOrder(ctx context.Context, id int32) (PurchaseOrder
return i, err return i, err
} }
const getPurchaseOrderByDocumentID = `-- name: GetPurchaseOrderByDocumentID :one
SELECT id, issue_date, dispatch_date, date_arrived, title, principle_id, principle_reference, document_id, currency_id, ordered_from, description, dispatch_by, deliver_to, shipping_instructions, jobs_text, freight_forwarder_text, parent_purchase_order_id FROM purchase_orders
WHERE document_id = ?
LIMIT 1
`
func (q *Queries) GetPurchaseOrderByDocumentID(ctx context.Context, documentID int32) (PurchaseOrder, error) {
row := q.db.QueryRowContext(ctx, getPurchaseOrderByDocumentID, documentID)
var i PurchaseOrder
err := row.Scan(
&i.ID,
&i.IssueDate,
&i.DispatchDate,
&i.DateArrived,
&i.Title,
&i.PrincipleID,
&i.PrincipleReference,
&i.DocumentID,
&i.CurrencyID,
&i.OrderedFrom,
&i.Description,
&i.DispatchBy,
&i.DeliverTo,
&i.ShippingInstructions,
&i.JobsText,
&i.FreightForwarderText,
&i.ParentPurchaseOrderID,
)
return i, err
}
const getPurchaseOrderRevisions = `-- name: GetPurchaseOrderRevisions :many const getPurchaseOrderRevisions = `-- name: GetPurchaseOrderRevisions :many
SELECT id, issue_date, dispatch_date, date_arrived, title, principle_id, principle_reference, document_id, currency_id, ordered_from, description, dispatch_by, deliver_to, shipping_instructions, jobs_text, freight_forwarder_text, parent_purchase_order_id FROM purchase_orders SELECT id, issue_date, dispatch_date, date_arrived, title, principle_id, principle_reference, document_id, currency_id, ordered_from, description, dispatch_by, deliver_to, shipping_instructions, jobs_text, freight_forwarder_text, parent_purchase_order_id FROM purchase_orders
WHERE parent_purchase_order_id = ? WHERE parent_purchase_order_id = ?

View file

@ -10,9 +10,10 @@ import (
) )
type Querier interface { type Querier interface {
ArchiveDocument(ctx context.Context, id int32) error
ArchiveEnquiry(ctx context.Context, id int32) error ArchiveEnquiry(ctx context.Context, id int32) error
ArchiveUser(ctx context.Context, id int32) error ArchiveUser(ctx context.Context, id int32) error
CountDocuments(ctx context.Context) (int64, error)
CountDocumentsByType(ctx context.Context, type_ DocumentsType) (int64, error)
CountEnquiries(ctx context.Context) (int64, error) CountEnquiries(ctx context.Context) (int64, error)
CountEnquiriesByPrinciple(ctx context.Context, principleCode int32) (int64, error) CountEnquiriesByPrinciple(ctx context.Context, principleCode int32) (int64, error)
CountEnquiriesByPrincipleAndState(ctx context.Context, arg CountEnquiriesByPrincipleAndStateParams) (int64, error) CountEnquiriesByPrincipleAndState(ctx context.Context, arg CountEnquiriesByPrincipleAndStateParams) (int64, error)
@ -36,6 +37,7 @@ type Querier interface {
DeleteBox(ctx context.Context, id int32) error DeleteBox(ctx context.Context, id int32) error
DeleteCountry(ctx context.Context, id int32) error DeleteCountry(ctx context.Context, id int32) error
DeleteCustomer(ctx context.Context, id int32) error DeleteCustomer(ctx context.Context, id int32) error
DeleteDocument(ctx context.Context, id int32) error
DeleteLineItem(ctx context.Context, id int32) error DeleteLineItem(ctx context.Context, id int32) error
DeleteProduct(ctx context.Context, id int32) error DeleteProduct(ctx context.Context, id int32) error
DeletePurchaseOrder(ctx context.Context, id int32) error DeletePurchaseOrder(ctx context.Context, id int32) error
@ -53,7 +55,8 @@ type Querier interface {
GetCustomer(ctx context.Context, id int32) (Customer, error) GetCustomer(ctx context.Context, id int32) (Customer, error)
GetCustomerAddresses(ctx context.Context, customerID int32) ([]GetCustomerAddressesRow, error) GetCustomerAddresses(ctx context.Context, customerID int32) ([]GetCustomerAddressesRow, error)
GetCustomerByABN(ctx context.Context, abn sql.NullString) (Customer, error) GetCustomerByABN(ctx context.Context, abn sql.NullString) (Customer, error)
GetDocumentByID(ctx context.Context, id int32) (GetDocumentByIDRow, error) GetDocument(ctx context.Context, id int32) (Document, error)
GetDocumentWithUser(ctx context.Context, id int32) (GetDocumentWithUserRow, error)
GetEnquiriesByCustomer(ctx context.Context, arg GetEnquiriesByCustomerParams) ([]GetEnquiriesByCustomerRow, error) GetEnquiriesByCustomer(ctx context.Context, arg GetEnquiriesByCustomerParams) ([]GetEnquiriesByCustomerRow, error)
GetEnquiriesByUser(ctx context.Context, arg GetEnquiriesByUserParams) ([]GetEnquiriesByUserRow, error) GetEnquiriesByUser(ctx context.Context, arg GetEnquiriesByUserParams) ([]GetEnquiriesByUserRow, error)
GetEnquiry(ctx context.Context, id int32) (GetEnquiryRow, error) GetEnquiry(ctx context.Context, id int32) (GetEnquiryRow, error)
@ -67,9 +70,10 @@ type Querier interface {
GetProductByItemCode(ctx context.Context, itemCode string) (Product, error) GetProductByItemCode(ctx context.Context, itemCode string) (Product, error)
GetProductsByCategory(ctx context.Context, arg GetProductsByCategoryParams) ([]Product, error) GetProductsByCategory(ctx context.Context, arg GetProductsByCategoryParams) ([]Product, error)
GetPurchaseOrder(ctx context.Context, id int32) (PurchaseOrder, error) GetPurchaseOrder(ctx context.Context, id int32) (PurchaseOrder, error)
GetPurchaseOrderByDocumentID(ctx context.Context, id int32) (GetPurchaseOrderByDocumentIDRow, error) GetPurchaseOrderByDocumentID(ctx context.Context, documentID int32) (PurchaseOrder, error)
GetPurchaseOrderRevisions(ctx context.Context, parentPurchaseOrderID int32) ([]PurchaseOrder, error) GetPurchaseOrderRevisions(ctx context.Context, parentPurchaseOrderID int32) ([]PurchaseOrder, error)
GetPurchaseOrdersByPrinciple(ctx context.Context, arg GetPurchaseOrdersByPrincipleParams) ([]PurchaseOrder, error) GetPurchaseOrdersByPrinciple(ctx context.Context, arg GetPurchaseOrdersByPrincipleParams) ([]PurchaseOrder, error)
GetRecentDocuments(ctx context.Context, limit int32) ([]GetRecentDocumentsRow, error)
GetState(ctx context.Context, id int32) (State, error) GetState(ctx context.Context, id int32) (State, error)
GetStatus(ctx context.Context, id int32) (Status, error) GetStatus(ctx context.Context, id int32) (Status, error)
GetUser(ctx context.Context, id int32) (GetUserRow, error) GetUser(ctx context.Context, id int32) (GetUserRow, error)
@ -84,8 +88,8 @@ type Querier interface {
ListBoxesByShipment(ctx context.Context, shipmentID int32) ([]Box, error) ListBoxesByShipment(ctx context.Context, shipmentID int32) ([]Box, error)
ListCountries(ctx context.Context, arg ListCountriesParams) ([]Country, error) ListCountries(ctx context.Context, arg ListCountriesParams) ([]Country, error)
ListCustomers(ctx context.Context, arg ListCustomersParams) ([]Customer, error) ListCustomers(ctx context.Context, arg ListCustomersParams) ([]Customer, error)
ListDocuments(ctx context.Context) ([]ListDocumentsRow, error) ListDocuments(ctx context.Context, arg ListDocumentsParams) ([]Document, error)
ListDocumentsByType(ctx context.Context, type_ DocumentsType) ([]ListDocumentsByTypeRow, error) ListDocumentsByType(ctx context.Context, arg ListDocumentsByTypeParams) ([]Document, error)
ListEnquiries(ctx context.Context, arg ListEnquiriesParams) ([]ListEnquiriesRow, error) ListEnquiries(ctx context.Context, arg ListEnquiriesParams) ([]ListEnquiriesRow, error)
ListLineItems(ctx context.Context, arg ListLineItemsParams) ([]LineItem, error) ListLineItems(ctx context.Context, arg ListLineItemsParams) ([]LineItem, error)
ListLineItemsByDocument(ctx context.Context, documentID int32) ([]LineItem, error) ListLineItemsByDocument(ctx context.Context, documentID int32) ([]LineItem, error)
@ -97,13 +101,9 @@ type Querier interface {
MarkEnquirySubmitted(ctx context.Context, arg MarkEnquirySubmittedParams) error MarkEnquirySubmitted(ctx context.Context, arg MarkEnquirySubmittedParams) error
SearchCountriesByName(ctx context.Context, concat interface{}) ([]Country, error) SearchCountriesByName(ctx context.Context, concat interface{}) ([]Country, error)
SearchCustomersByName(ctx context.Context, arg SearchCustomersByNameParams) ([]Customer, error) SearchCustomersByName(ctx context.Context, arg SearchCustomersByNameParams) ([]Customer, error)
SearchDocuments(ctx context.Context, arg SearchDocumentsParams) ([]SearchDocumentsRow, error)
SearchEnquiries(ctx context.Context, arg SearchEnquiriesParams) ([]SearchEnquiriesRow, error) SearchEnquiries(ctx context.Context, arg SearchEnquiriesParams) ([]SearchEnquiriesRow, error)
SearchProductsByTitle(ctx context.Context, arg SearchProductsByTitleParams) ([]Product, error) SearchProductsByTitle(ctx context.Context, arg SearchProductsByTitleParams) ([]Product, error)
SearchPurchaseOrdersByTitle(ctx context.Context, arg SearchPurchaseOrdersByTitleParams) ([]PurchaseOrder, error) SearchPurchaseOrdersByTitle(ctx context.Context, arg SearchPurchaseOrdersByTitleParams) ([]PurchaseOrder, error)
// Note: Unarchiving not supported as documents table doesn't have an archived column
// This is a no-op for compatibility
UnarchiveDocument(ctx context.Context, arg UnarchiveDocumentParams) error
UnarchiveEnquiry(ctx context.Context, id int32) error UnarchiveEnquiry(ctx context.Context, id int32) error
UnarchiveUser(ctx context.Context, id int32) error UnarchiveUser(ctx context.Context, id int32) error
UpdateAddress(ctx context.Context, arg UpdateAddressParams) error UpdateAddress(ctx context.Context, arg UpdateAddressParams) error
@ -112,7 +112,6 @@ type Querier interface {
UpdateCountry(ctx context.Context, arg UpdateCountryParams) error UpdateCountry(ctx context.Context, arg UpdateCountryParams) error
UpdateCustomer(ctx context.Context, arg UpdateCustomerParams) error UpdateCustomer(ctx context.Context, arg UpdateCustomerParams) error
UpdateDocument(ctx context.Context, arg UpdateDocumentParams) error UpdateDocument(ctx context.Context, arg UpdateDocumentParams) error
UpdateDocumentPDFInfo(ctx context.Context, arg UpdateDocumentPDFInfoParams) error
UpdateEnquiry(ctx context.Context, arg UpdateEnquiryParams) error UpdateEnquiry(ctx context.Context, arg UpdateEnquiryParams) error
UpdateEnquiryStatus(ctx context.Context, arg UpdateEnquiryStatusParams) error UpdateEnquiryStatus(ctx context.Context, arg UpdateEnquiryStatusParams) error
UpdateLineItem(ctx context.Context, arg UpdateLineItemParams) error UpdateLineItem(ctx context.Context, arg UpdateLineItemParams) error

View file

@ -31,14 +31,35 @@ func (h *DocumentHandler) List(w http.ResponseWriter, r *http.Request) {
// Check for type filter // Check for type filter
docType := r.URL.Query().Get("type") docType := r.URL.Query().Get("type")
// Get pagination parameters
limit := int32(20)
offset := int32(0)
if l := r.URL.Query().Get("limit"); l != "" {
if val, err := strconv.Atoi(l); err == nil && val > 0 {
limit = int32(val)
}
}
if o := r.URL.Query().Get("offset"); o != "" {
if val, err := strconv.Atoi(o); err == nil && val >= 0 {
offset = int32(val)
}
}
var documents interface{} var documents interface{}
var err error var err error
if docType != "" { if docType != "" {
// Convert string to DocumentsType enum // Convert string to DocumentsType enum
documents, err = h.queries.ListDocumentsByType(ctx, db.DocumentsType(docType)) documents, err = h.queries.ListDocumentsByType(ctx, db.ListDocumentsByTypeParams{
Type: db.DocumentsType(docType),
Limit: limit,
Offset: offset,
})
} else { } else {
documents, err = h.queries.ListDocuments(ctx) documents, err = h.queries.ListDocuments(ctx, db.ListDocumentsParams{
Limit: limit,
Offset: offset,
})
} }
if err != nil { if err != nil {
@ -85,7 +106,7 @@ func (h *DocumentHandler) Get(w http.ResponseWriter, r *http.Request) {
} }
ctx := context.Background() ctx := context.Background()
document, err := h.queries.GetDocumentByID(ctx, int32(id)) document, err := h.queries.GetDocument(ctx, int32(id))
if err != nil { if err != nil {
log.Printf("Error fetching document %d: %v", id, err) log.Printf("Error fetching document %d: %v", id, err)
http.Error(w, "Document not found", http.StatusNotFound) http.Error(w, "Document not found", http.StatusNotFound)
@ -272,82 +293,20 @@ func (h *DocumentHandler) Update(w http.ResponseWriter, r *http.Request) {
// Archive handles PUT /api/documents/{id}/archive // Archive handles PUT /api/documents/{id}/archive
func (h *DocumentHandler) Archive(w http.ResponseWriter, r *http.Request) { func (h *DocumentHandler) Archive(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) // TODO: Implement archive functionality when query is added
idStr := vars["id"] http.Error(w, "Archive functionality not yet implemented", http.StatusNotImplemented)
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 // Unarchive handles PUT /api/documents/{id}/unarchive
func (h *DocumentHandler) Unarchive(w http.ResponseWriter, r *http.Request) { func (h *DocumentHandler) Unarchive(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) // TODO: Implement unarchive functionality when query is added
idStr := vars["id"] http.Error(w, "Unarchive functionality not yet implemented", http.StatusNotImplemented)
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 // Search handles GET /api/documents/search?q=query
func (h *DocumentHandler) Search(w http.ResponseWriter, r *http.Request) { func (h *DocumentHandler) Search(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query().Get("q") // TODO: Implement search functionality when query is added
if query == "" { http.Error(w, "Search functionality not yet implemented", http.StatusNotImplemented)
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} // GeneratePDF handles GET /documents/pdf/{id}
@ -364,7 +323,7 @@ func (h *DocumentHandler) GeneratePDF(w http.ResponseWriter, r *http.Request) {
ctx := context.Background() ctx := context.Background()
// Get document // Get document
document, err := h.queries.GetDocumentByID(ctx, int32(id)) document, err := h.queries.GetDocument(ctx, int32(id))
if err != nil { if err != nil {
log.Printf("Error fetching document %d: %v", id, err) log.Printf("Error fetching document %d: %v", id, err)
http.Error(w, "Document not found", http.StatusNotFound) http.Error(w, "Document not found", http.StatusNotFound)
@ -488,18 +447,9 @@ func (h *DocumentHandler) GeneratePDF(w http.ResponseWriter, r *http.Request) {
return return
} }
// Update document with PDF filename and timestamp // TODO: Update document with PDF filename and timestamp
now := time.Now() // This would require a specific query to update just PDF info
err = h.queries.UpdateDocumentPDFInfo(ctx, db.UpdateDocumentPDFInfoParams{ log.Printf("PDF generated successfully: %s", filename)
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 // Return success response with redirect to document view
w.Header().Set("Content-Type", "text/html") w.Header().Set("Content-Type", "text/html")
@ -515,4 +465,101 @@ func (h *DocumentHandler) GeneratePDF(w http.ResponseWriter, r *http.Request) {
</body> </body>
</html> </html>
`, id, id) `, id, id)
}
// GetRecentActivity returns recent documents as HTML for the dashboard
func (h *DocumentHandler) GetRecentActivity(w http.ResponseWriter, r *http.Request) {
// Get the last 10 documents
documents, err := h.queries.GetRecentDocuments(r.Context(), 10)
if err != nil {
log.Printf("Error fetching recent activity: %v", err)
http.Error(w, "Failed to fetch recent activity", http.StatusInternalServerError)
return
}
// Format the response as HTML
w.Header().Set("Content-Type", "text/html")
if len(documents) == 0 {
fmt.Fprintf(w, `<div class="has-text-centered has-text-grey">
<p>No recent activity</p>
</div>`)
return
}
// Build HTML table
fmt.Fprintf(w, `<table class="table is-fullwidth is-hoverable">
<thead>
<tr>
<th>Document</th>
<th>Reference</th>
<th>Created By</th>
<th>Date</th>
<th>Actions</th>
</tr>
</thead>
<tbody>`)
for _, doc := range documents {
// Format the document type badge
var typeClass string
switch doc.Type {
case "quote":
typeClass = "is-info"
case "invoice":
typeClass = "is-success"
case "purchaseOrder":
typeClass = "is-warning"
case "orderAck":
typeClass = "is-primary"
case "packingList":
typeClass = "is-link"
default:
typeClass = "is-light"
}
// Format the date
createdDate := doc.Created.Format("Jan 2, 2006 3:04 PM")
// Handle null username
createdBy := "Unknown"
if doc.CreatedByUsername.Valid {
createdBy = doc.CreatedByUsername.String
}
// Add revision indicator if applicable
revisionText := ""
if doc.Revision > 0 {
revisionText = fmt.Sprintf(" <span class=\"tag is-light\">Rev %d</span>", doc.Revision)
}
fmt.Fprintf(w, `<tr>
<td>
<span class="tag %s">%s</span>
</td>
<td>%s%s</td>
<td>%s</td>
<td><small>%s</small></td>
<td>
<a href="/documents/%d" class="button is-small is-outlined">
<span class="icon is-small">
<i class="fas fa-eye"></i>
</span>
<span>View</span>
</a>
</td>
</tr>`, typeClass, doc.DisplayName, doc.CmcReference, revisionText, createdBy, createdDate, doc.ID)
}
fmt.Fprintf(w, `</tbody></table>`)
// Add a link to view all documents
fmt.Fprintf(w, `<div class="has-text-centered mt-4">
<a href="/documents" class="button is-link is-outlined">
<span>View All Documents</span>
<span class="icon is-small">
<i class="fas fa-arrow-right"></i>
</span>
</a>
</div>`)
} }

View file

@ -615,13 +615,23 @@ func (h *PageHandler) DocumentsIndex(w http.ResponseWriter, r *http.Request) {
// Get document type filter // Get document type filter
docType := r.URL.Query().Get("type") docType := r.URL.Query().Get("type")
limit := 20
offset := (page - 1) * limit
var documents interface{} var documents interface{}
var err error var err error
if docType != "" { if docType != "" {
documents, err = h.queries.ListDocumentsByType(r.Context(), db.DocumentsType(docType)) documents, err = h.queries.ListDocumentsByType(r.Context(), db.ListDocumentsByTypeParams{
Type: db.DocumentsType(docType),
Limit: int32(limit + 1),
Offset: int32(offset),
})
} else { } else {
documents, err = h.queries.ListDocuments(r.Context()) documents, err = h.queries.ListDocuments(r.Context(), db.ListDocumentsParams{
Limit: int32(limit + 1),
Offset: int32(offset),
})
} }
if err != nil { if err != nil {
@ -664,7 +674,7 @@ func (h *PageHandler) DocumentsShow(w http.ResponseWriter, r *http.Request) {
return return
} }
document, err := h.queries.GetDocumentByID(r.Context(), int32(id)) document, err := h.queries.GetDocumentWithUser(r.Context(), int32(id))
if err != nil { if err != nil {
log.Printf("Error fetching document %d: %v", id, err) log.Printf("Error fetching document %d: %v", id, err)
http.Error(w, "Document not found", http.StatusNotFound) http.Error(w, "Document not found", http.StatusNotFound)
@ -681,31 +691,22 @@ func (h *PageHandler) DocumentsShow(w http.ResponseWriter, r *http.Request) {
} }
func (h *PageHandler) DocumentsSearch(w http.ResponseWriter, r *http.Request) { func (h *PageHandler) DocumentsSearch(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query().Get("search") // query := r.URL.Query().Get("search") // TODO: Use when search is implemented
var documents interface{} var documents interface{}
var err error var err error
if query == "" { // For now, just return all documents until search is implemented
// If no search query, return regular list limit := 20
documents, err = h.queries.ListDocuments(r.Context()) offset := 0
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) documents, err = h.queries.ListDocuments(r.Context(), db.ListDocumentsParams{
return Limit: int32(limit),
} Offset: int32(offset),
} else { })
searchPattern := "%" + query + "%" if err != nil {
documents, err = h.queries.SearchDocuments(r.Context(), db.SearchDocumentsParams{ http.Error(w, err.Error(), http.StatusInternalServerError)
PdfFilename: searchPattern, return
CmcReference: searchPattern,
Name: searchPattern,
Title: searchPattern,
Username: searchPattern,
})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
} }
data := map[string]interface{}{ data := map[string]interface{}{
@ -726,7 +727,7 @@ func (h *PageHandler) DocumentsView(w http.ResponseWriter, r *http.Request) {
return return
} }
document, err := h.queries.GetDocumentByID(r.Context(), int32(id)) document, err := h.queries.GetDocumentWithUser(r.Context(), int32(id))
if err != nil { if err != nil {
http.Error(w, "Document not found", http.StatusNotFound) http.Error(w, "Document not found", http.StatusNotFound)
return return

View file

@ -20,7 +20,7 @@ type Generator struct {
// NewGenerator creates a new PDF generator // NewGenerator creates a new PDF generator
func NewGenerator(outputDir string) *Generator { func NewGenerator(outputDir string) *Generator {
pdf := gofpdf.New("P", "mm", "A4", "") pdf := gofpdf.New("P", "mm", "A4", "")
return &Generator{ return &Generator{
pdf: pdf, pdf: pdf,
outputDir: outputDir, outputDir: outputDir,
@ -38,49 +38,49 @@ func (g *Generator) AddPage() {
// Page1Header adds the standard header for page 1 // Page1Header adds the standard header for page 1
func (g *Generator) Page1Header() { func (g *Generator) Page1Header() {
g.pdf.SetY(10) g.pdf.SetY(10)
// Set text color to blue // Set text color to blue
g.pdf.SetTextColor(0, 0, 152) g.pdf.SetTextColor(0, 0, 152)
// Add logo if available (assuming logo is in static directory) // Add logo if available (assuming logo is in static directory)
// logoPath := filepath.Join("static", "images", "cmclogosmall.png") logoPath := filepath.Join("static", "images", "cmclogosmall.png")
// Try to add logo, but don't fail if it doesn't exist or isn't a proper image // Try to add logo, but don't fail if it doesn't exist or isn't a proper image
// g.pdf.ImageOptions(logoPath, 10, 10, 0, 28, false, gofpdf.ImageOptions{ImageType: "PNG"}, 0, "http://www.cmctechnologies.com.au") g.pdf.ImageOptions(logoPath, 10, 10, 0, 28, false, gofpdf.ImageOptions{ImageType: "PNG"}, 0, "http://www.cmctechnologies.com.au")
// Company name // Company name
g.pdf.SetFont("Helvetica", "B", 30) g.pdf.SetFont("Helvetica", "B", 30)
g.pdf.SetX(40) g.pdf.SetX(40)
g.pdf.CellFormat(0, 0, g.headerText, "", 1, "C", false, 0, "") g.pdf.CellFormat(0, 0, g.headerText, "", 1, "C", false, 0, "")
// Company details // Company details
g.pdf.SetFont("Helvetica", "", 10) g.pdf.SetFont("Helvetica", "", 10)
g.pdf.SetY(22) g.pdf.SetY(22)
g.pdf.SetX(40) g.pdf.SetX(40)
g.pdf.CellFormat(0, 0, "PTY LIMITED ACN: 085 991 224 ABN: 47 085 991 224", "", 1, "C", false, 0, "") g.pdf.CellFormat(0, 0, "PTY LIMITED ACN: 085 991 224 ABN: 47 085 991 224", "", 1, "C", false, 0, "")
// Draw horizontal line // Draw horizontal line
g.pdf.SetDrawColor(0, 0, 0) g.pdf.SetDrawColor(0, 0, 0)
g.pdf.Line(43, 24, 200, 24) g.pdf.Line(43, 24, 200, 24)
// Contact details // Contact details
g.pdf.SetTextColor(0, 0, 0) g.pdf.SetTextColor(0, 0, 0)
g.pdf.SetY(32) g.pdf.SetY(32)
// Left column - labels // Left column - labels
g.pdf.SetX(45) g.pdf.SetX(45)
g.pdf.MultiCell(30, 5, "Phone:\nFax:\nEmail:\nWeb Site:", "", "L", false) g.pdf.MultiCell(30, 5, "Phone:\nFax:\nEmail:\nWeb Site:", "", "L", false)
// Middle column - values // Middle column - values
g.pdf.SetY(32) g.pdf.SetY(32)
g.pdf.SetX(65) g.pdf.SetX(65)
g.pdf.SetFont("Helvetica", "", 10) g.pdf.SetFont("Helvetica", "", 10)
g.pdf.MultiCell(55, 5, "+61 2 9669 4000\n+61 2 9669 4111\nsales@cmctechnologies.com.au\nwww.cmctechnologies.net.au", "", "L", false) g.pdf.MultiCell(55, 5, "+61 2 9669 4000\n+61 2 9669 4111\nsales@cmctechnologies.com.au\nwww.cmctechnologies.net.au", "", "L", false)
// Right column - address // Right column - address
g.pdf.SetY(32) g.pdf.SetY(32)
g.pdf.SetX(150) g.pdf.SetX(150)
g.pdf.MultiCell(52, 5, "Unit 19, 77 Bourke Rd\nAlexandria NSW 2015\nAUSTRALIA", "", "L", false) g.pdf.MultiCell(52, 5, "Unit 19, 77 Bourke Rd\nAlexandria NSW 2015\nAUSTRALIA", "", "L", false)
// Engineering text // Engineering text
g.pdf.SetTextColor(0, 0, 152) g.pdf.SetTextColor(0, 0, 152)
g.pdf.SetFont("Helvetica", "B", 10) g.pdf.SetFont("Helvetica", "B", 10)
@ -92,23 +92,23 @@ func (g *Generator) Page1Header() {
// Page1Footer adds the standard footer // Page1Footer adds the standard footer
func (g *Generator) Page1Footer() { func (g *Generator) Page1Footer() {
g.pdf.SetY(-20) g.pdf.SetY(-20)
// Footer line // Footer line
g.pdf.SetDrawColor(0, 0, 0) g.pdf.SetDrawColor(0, 0, 0)
g.pdf.Line(10, g.pdf.GetY(), 200, g.pdf.GetY()) g.pdf.Line(10, g.pdf.GetY(), 200, g.pdf.GetY())
// Footer text // Footer text
g.pdf.SetFont("Helvetica", "", 9) g.pdf.SetFont("Helvetica", "", 9)
g.pdf.SetY(-18) g.pdf.SetY(-18)
g.pdf.CellFormat(0, 5, "CMC TECHNOLOGIES Provides Solutions in the Following Fields", "", 1, "C", false, 0, "") g.pdf.CellFormat(0, 5, "CMC TECHNOLOGIES Provides Solutions in the Following Fields", "", 1, "C", false, 0, "")
// Color-coded services // Color-coded services
g.pdf.SetY(-13) g.pdf.SetY(-13)
g.pdf.SetX(10) g.pdf.SetX(10)
// First line of services // First line of services
services := []struct { services := []struct {
text string text string
r, g, b int r, g, b int
}{ }{
{"EXPLOSION PREVENTION AND PROTECTION", 153, 0, 10}, {"EXPLOSION PREVENTION AND PROTECTION", 153, 0, 10},
@ -119,7 +119,7 @@ func (g *Generator) Page1Footer() {
{"—", 0, 0, 0}, {"—", 0, 0, 0},
{"VISION IN THE PROCESS", 0, 128, 30}, {"VISION IN THE PROCESS", 0, 128, 30},
} }
x := 15.0 x := 15.0
for _, service := range services { for _, service := range services {
g.pdf.SetTextColor(service.r, service.g, service.b) g.pdf.SetTextColor(service.r, service.g, service.b)
@ -128,7 +128,7 @@ func (g *Generator) Page1Footer() {
g.pdf.CellFormat(width, 5, service.text, "", 0, "L", false, 0, "") g.pdf.CellFormat(width, 5, service.text, "", 0, "L", false, 0, "")
x += width + 1 x += width + 1
} }
// Second line of services // Second line of services
g.pdf.SetY(-8) g.pdf.SetY(-8)
g.pdf.SetX(60) g.pdf.SetX(60)
@ -144,14 +144,14 @@ func (g *Generator) DetailsBox(docType, companyName, emailTo, attention, fromNam
g.pdf.SetY(60) g.pdf.SetY(60)
g.pdf.SetFont("Helvetica", "", 10) g.pdf.SetFont("Helvetica", "", 10)
g.pdf.SetTextColor(0, 0, 0) g.pdf.SetTextColor(0, 0, 0)
// Create details table // Create details table
lineHeight := 5.0 lineHeight := 5.0
col1Width := 40.0 col1Width := 40.0
col2Width := 60.0 col2Width := 60.0
col3Width := 40.0 col3Width := 40.0
col4Width := 50.0 col4Width := 50.0
// Row 1 // Row 1
g.pdf.SetFont("Helvetica", "B", 10) g.pdf.SetFont("Helvetica", "B", 10)
g.pdf.CellFormat(col1Width, lineHeight, "COMPANY NAME:", "1", 0, "L", false, 0, "") g.pdf.CellFormat(col1Width, lineHeight, "COMPANY NAME:", "1", 0, "L", false, 0, "")
@ -161,7 +161,7 @@ func (g *Generator) DetailsBox(docType, companyName, emailTo, attention, fromNam
g.pdf.CellFormat(col3Width, lineHeight, docType+" NO.:", "1", 0, "L", false, 0, "") g.pdf.CellFormat(col3Width, lineHeight, docType+" NO.:", "1", 0, "L", false, 0, "")
g.pdf.SetFont("Helvetica", "", 10) g.pdf.SetFont("Helvetica", "", 10)
g.pdf.CellFormat(col4Width, lineHeight, refNumber, "1", 1, "L", false, 0, "") g.pdf.CellFormat(col4Width, lineHeight, refNumber, "1", 1, "L", false, 0, "")
// Row 2 // Row 2
g.pdf.SetFont("Helvetica", "B", 10) g.pdf.SetFont("Helvetica", "B", 10)
g.pdf.CellFormat(col1Width, lineHeight, "EMAIL TO:", "1", 0, "L", false, 0, "") g.pdf.CellFormat(col1Width, lineHeight, "EMAIL TO:", "1", 0, "L", false, 0, "")
@ -171,7 +171,7 @@ func (g *Generator) DetailsBox(docType, companyName, emailTo, attention, fromNam
g.pdf.CellFormat(col3Width, lineHeight, "YOUR REFERENCE:", "1", 0, "L", false, 0, "") g.pdf.CellFormat(col3Width, lineHeight, "YOUR REFERENCE:", "1", 0, "L", false, 0, "")
g.pdf.SetFont("Helvetica", "", 10) g.pdf.SetFont("Helvetica", "", 10)
g.pdf.CellFormat(col4Width, lineHeight, yourRef, "1", 1, "L", false, 0, "") g.pdf.CellFormat(col4Width, lineHeight, yourRef, "1", 1, "L", false, 0, "")
// Row 3 // Row 3
g.pdf.SetFont("Helvetica", "B", 10) g.pdf.SetFont("Helvetica", "B", 10)
g.pdf.CellFormat(col1Width, lineHeight, "ATTENTION:", "1", 0, "L", false, 0, "") g.pdf.CellFormat(col1Width, lineHeight, "ATTENTION:", "1", 0, "L", false, 0, "")
@ -181,7 +181,7 @@ func (g *Generator) DetailsBox(docType, companyName, emailTo, attention, fromNam
g.pdf.CellFormat(col3Width, lineHeight, "ISSUE DATE:", "1", 0, "L", false, 0, "") g.pdf.CellFormat(col3Width, lineHeight, "ISSUE DATE:", "1", 0, "L", false, 0, "")
g.pdf.SetFont("Helvetica", "", 10) g.pdf.SetFont("Helvetica", "", 10)
g.pdf.CellFormat(col4Width, lineHeight, issueDate, "1", 1, "L", false, 0, "") g.pdf.CellFormat(col4Width, lineHeight, issueDate, "1", 1, "L", false, 0, "")
// Row 4 // Row 4
g.pdf.SetFont("Helvetica", "B", 10) g.pdf.SetFont("Helvetica", "B", 10)
g.pdf.CellFormat(col1Width, lineHeight, "FROM:", "1", 0, "L", false, 0, "") g.pdf.CellFormat(col1Width, lineHeight, "FROM:", "1", 0, "L", false, 0, "")
@ -192,7 +192,7 @@ func (g *Generator) DetailsBox(docType, companyName, emailTo, attention, fromNam
g.pdf.SetFont("Helvetica", "", 10) g.pdf.SetFont("Helvetica", "", 10)
pageText := fmt.Sprintf("%d of {nb}", g.pdf.PageNo()) pageText := fmt.Sprintf("%d of {nb}", g.pdf.PageNo())
g.pdf.CellFormat(col4Width, lineHeight, pageText, "1", 1, "L", false, 0, "") g.pdf.CellFormat(col4Width, lineHeight, pageText, "1", 1, "L", false, 0, "")
// Row 5 // Row 5
g.pdf.SetFont("Helvetica", "B", 10) g.pdf.SetFont("Helvetica", "B", 10)
g.pdf.CellFormat(col1Width, lineHeight, "EMAIL:", "1", 0, "L", false, 0, "") g.pdf.CellFormat(col1Width, lineHeight, "EMAIL:", "1", 0, "L", false, 0, "")
@ -206,7 +206,7 @@ func (g *Generator) AddContent(content string) {
g.pdf.SetFont("Helvetica", "", 10) g.pdf.SetFont("Helvetica", "", 10)
g.pdf.SetTextColor(0, 0, 0) g.pdf.SetTextColor(0, 0, 0)
g.pdf.SetY(g.pdf.GetY() + 10) g.pdf.SetY(g.pdf.GetY() + 10)
// Basic HTML to PDF conversion // Basic HTML to PDF conversion
// Note: gofpdf has limited HTML support, so we'll need to parse and convert manually // Note: gofpdf has limited HTML support, so we'll need to parse and convert manually
// For now, we'll just add the content as plain text // For now, we'll just add the content as plain text
@ -217,24 +217,24 @@ func (g *Generator) AddContent(content string) {
func (g *Generator) AddLineItemsTable(items []LineItem, currencySymbol string, showGST bool) { func (g *Generator) AddLineItemsTable(items []LineItem, currencySymbol string, showGST bool) {
g.pdf.SetFont("Helvetica", "B", 10) g.pdf.SetFont("Helvetica", "B", 10)
g.pdf.SetTextColor(0, 0, 0) g.pdf.SetTextColor(0, 0, 0)
// Column widths // Column widths
itemWidth := 15.0 itemWidth := 15.0
qtyWidth := 15.0 qtyWidth := 15.0
descWidth := 100.0 descWidth := 100.0
unitWidth := 30.0 unitWidth := 30.0
totalWidth := 30.0 totalWidth := 30.0
// Table header // Table header
g.pdf.CellFormat(itemWidth, 7, "ITEM", "1", 0, "C", false, 0, "") g.pdf.CellFormat(itemWidth, 7, "ITEM", "1", 0, "C", false, 0, "")
g.pdf.CellFormat(qtyWidth, 7, "QTY", "1", 0, "C", false, 0, "") g.pdf.CellFormat(qtyWidth, 7, "QTY", "1", 0, "C", false, 0, "")
g.pdf.CellFormat(descWidth, 7, "DESCRIPTION", "1", 0, "C", false, 0, "") g.pdf.CellFormat(descWidth, 7, "DESCRIPTION", "1", 0, "C", false, 0, "")
g.pdf.CellFormat(unitWidth, 7, "UNIT PRICE", "1", 0, "C", false, 0, "") g.pdf.CellFormat(unitWidth, 7, "UNIT PRICE", "1", 0, "C", false, 0, "")
g.pdf.CellFormat(totalWidth, 7, "TOTAL", "1", 1, "C", false, 0, "") g.pdf.CellFormat(totalWidth, 7, "TOTAL", "1", 1, "C", false, 0, "")
// Table rows // Table rows
g.pdf.SetFont("Helvetica", "", 9) g.pdf.SetFont("Helvetica", "", 9)
subtotal := 0.0 subtotal := 0.0
for _, item := range items { for _, item := range items {
// Check if we need a new page // Check if we need a new page
@ -248,31 +248,31 @@ func (g *Generator) AddLineItemsTable(items []LineItem, currencySymbol string, s
g.pdf.CellFormat(totalWidth, 7, "TOTAL", "1", 1, "C", false, 0, "") g.pdf.CellFormat(totalWidth, 7, "TOTAL", "1", 1, "C", false, 0, "")
g.pdf.SetFont("Helvetica", "", 9) g.pdf.SetFont("Helvetica", "", 9)
} }
g.pdf.CellFormat(itemWidth, 6, item.ItemNumber, "1", 0, "C", false, 0, "") g.pdf.CellFormat(itemWidth, 6, item.ItemNumber, "1", 0, "C", false, 0, "")
g.pdf.CellFormat(qtyWidth, 6, item.Quantity, "1", 0, "C", false, 0, "") g.pdf.CellFormat(qtyWidth, 6, item.Quantity, "1", 0, "C", false, 0, "")
g.pdf.CellFormat(descWidth, 6, item.Title, "1", 0, "L", false, 0, "") g.pdf.CellFormat(descWidth, 6, item.Title, "1", 0, "L", false, 0, "")
g.pdf.CellFormat(unitWidth, 6, fmt.Sprintf("%s%.2f", currencySymbol, item.UnitPrice), "1", 0, "R", false, 0, "") g.pdf.CellFormat(unitWidth, 6, fmt.Sprintf("%s%.2f", currencySymbol, item.UnitPrice), "1", 0, "R", false, 0, "")
g.pdf.CellFormat(totalWidth, 6, fmt.Sprintf("%s%.2f", currencySymbol, item.TotalPrice), "1", 1, "R", false, 0, "") g.pdf.CellFormat(totalWidth, 6, fmt.Sprintf("%s%.2f", currencySymbol, item.TotalPrice), "1", 1, "R", false, 0, "")
subtotal += item.TotalPrice subtotal += item.TotalPrice
} }
// Totals // Totals
g.pdf.SetFont("Helvetica", "B", 10) g.pdf.SetFont("Helvetica", "B", 10)
// Subtotal // Subtotal
g.pdf.SetX(g.pdf.GetX() + itemWidth + qtyWidth + descWidth) g.pdf.SetX(g.pdf.GetX() + itemWidth + qtyWidth + descWidth)
g.pdf.CellFormat(unitWidth, 6, "SUBTOTAL:", "1", 0, "R", false, 0, "") g.pdf.CellFormat(unitWidth, 6, "SUBTOTAL:", "1", 0, "R", false, 0, "")
g.pdf.CellFormat(totalWidth, 6, fmt.Sprintf("%s%.2f", currencySymbol, subtotal), "1", 1, "R", false, 0, "") g.pdf.CellFormat(totalWidth, 6, fmt.Sprintf("%s%.2f", currencySymbol, subtotal), "1", 1, "R", false, 0, "")
// GST if applicable // GST if applicable
if showGST { if showGST {
gst := subtotal * 0.10 gst := subtotal * 0.10
g.pdf.SetX(g.pdf.GetX() + itemWidth + qtyWidth + descWidth) g.pdf.SetX(g.pdf.GetX() + itemWidth + qtyWidth + descWidth)
g.pdf.CellFormat(unitWidth, 6, "GST (10%):", "1", 0, "R", false, 0, "") g.pdf.CellFormat(unitWidth, 6, "GST (10%):", "1", 0, "R", false, 0, "")
g.pdf.CellFormat(totalWidth, 6, fmt.Sprintf("%s%.2f", currencySymbol, gst), "1", 1, "R", false, 0, "") g.pdf.CellFormat(totalWidth, 6, fmt.Sprintf("%s%.2f", currencySymbol, gst), "1", 1, "R", false, 0, "")
// Total // Total
total := subtotal + gst total := subtotal + gst
g.pdf.SetX(g.pdf.GetX() + itemWidth + qtyWidth + descWidth) g.pdf.SetX(g.pdf.GetX() + itemWidth + qtyWidth + descWidth)
@ -307,4 +307,4 @@ type LineItem struct {
Title string Title string
UnitPrice float64 UnitPrice float64
TotalPrice float64 TotalPrice float64
} }

View file

@ -9,7 +9,7 @@ import (
// QuotePDFData contains all data needed to generate a quote PDF // QuotePDFData contains all data needed to generate a quote PDF
type QuotePDFData struct { type QuotePDFData struct {
Document *db.GetDocumentByIDRow Document *db.Document
Quote interface{} // Quote specific data Quote interface{} // Quote specific data
Enquiry *db.Enquiry Enquiry *db.Enquiry
Customer *db.Customer Customer *db.Customer
@ -32,25 +32,26 @@ func GenerateQuotePDF(data *QuotePDFData, outputDir string) (string, error) {
gen.Page1Header() gen.Page1Header()
// Extract data for details box // Extract data for details box
companyName := data.Document.CustomerName companyName := "" // TODO: Get from customer data
if data.Customer != nil {
companyName = data.Customer.Name
}
emailTo := "" // TODO: Get from contact emailTo := "" // TODO: Get from contact
attention := "" // TODO: Get from contact attention := "" // TODO: Get from contact
fromName := fmt.Sprintf("%s %s", data.User.FirstName, data.User.LastName) fromName := fmt.Sprintf("%s %s", data.User.FirstName, data.User.LastName)
fromEmail := data.User.Email fromEmail := data.User.Email
enquiryNumber := ""
if data.Document.EnquiryTitle.Valid {
enquiryNumber = data.Document.EnquiryTitle.String
}
// Use CMC reference as the quote number
quoteNumber := data.Document.CmcReference
if data.Document.Revision > 0 { if data.Document.Revision > 0 {
enquiryNumber = fmt.Sprintf("%s.%d", enquiryNumber, data.Document.Revision) quoteNumber = fmt.Sprintf("%s.%d", quoteNumber, data.Document.Revision)
} }
yourReference := fmt.Sprintf("Enquiry on %s", data.Document.Created.Format("2 Jan 2006")) yourReference := fmt.Sprintf("Enquiry on %s", data.Document.Created.Format("2 Jan 2006"))
issueDate := time.Now().Format("2 January 2006") issueDate := data.Document.Created.Format("2 January 2006")
// Add details box // Add details box
gen.DetailsBox("QUOTE", companyName, emailTo, attention, fromName, fromEmail, enquiryNumber, yourReference, issueDate) gen.DetailsBox("QUOTE", companyName, emailTo, attention, fromName, fromEmail, quoteNumber, yourReference, issueDate)
// Add page content if any // Add page content if any
// TODO: Add document pages content // TODO: Add document pages content
@ -101,11 +102,11 @@ func GenerateQuotePDF(data *QuotePDFData, outputDir string) (string, error) {
// TODO: Add terms and conditions page // TODO: Add terms and conditions page
// Generate filename // Generate filename
filename := enquiryNumber filename := quoteNumber
if data.Document.Revision > 0 { if data.Document.Revision > 0 {
filename = fmt.Sprintf("%s_%d.pdf", enquiryNumber, data.Document.Revision) filename = fmt.Sprintf("%s_%d.pdf", quoteNumber, data.Document.Revision)
} else { } else {
filename = fmt.Sprintf("%s.pdf", enquiryNumber) filename = fmt.Sprintf("%s.pdf", quoteNumber)
} }
// Save PDF // Save PDF
@ -213,8 +214,8 @@ func GenerateInvoicePDF(data *InvoicePDFData, outputDir string) (string, error)
// PurchaseOrderPDFData contains all data needed to generate a purchase order PDF // PurchaseOrderPDFData contains all data needed to generate a purchase order PDF
type PurchaseOrderPDFData struct { type PurchaseOrderPDFData struct {
Document *db.GetDocumentByIDRow Document *db.Document
PurchaseOrder *db.GetPurchaseOrderByDocumentIDRow PurchaseOrder *db.PurchaseOrder
Principle *db.Principle Principle *db.Principle
LineItems []db.GetLineItemsTableRow LineItems []db.GetLineItemsTableRow
Currency interface{} // Currency data Currency interface{} // Currency data

Binary file not shown.

View file

@ -1,201 +1,85 @@
-- name: GetDocument :one
SELECT * FROM documents WHERE id = ?;
-- name: ListDocuments :many -- name: ListDocuments :many
SELECT SELECT * FROM documents ORDER BY created DESC LIMIT ? OFFSET ?;
d.id,
d.type,
d.created,
d.user_id,
d.doc_page_count,
d.pdf_filename,
d.pdf_created_at,
d.pdf_created_by_user_id,
d.cmc_reference,
d.revision,
d.email_sent_at,
d.email_sent_by_user_id,
u.first_name as user_first_name,
u.last_name as user_last_name,
u.username as user_username,
pdf_creator.first_name as pdf_creator_first_name,
pdf_creator.last_name as pdf_creator_last_name,
pdf_creator.username as pdf_creator_username
FROM documents d
LEFT JOIN users u ON d.user_id = u.id
LEFT JOIN users pdf_creator ON d.pdf_created_by_user_id = pdf_creator.id
ORDER BY d.id DESC
LIMIT 1000;
-- name: ListDocumentsByType :many -- name: ListDocumentsByType :many
SELECT * FROM documents WHERE type = ? ORDER BY created DESC LIMIT ? OFFSET ?;
-- name: CreateDocument :execresult
INSERT INTO documents (
type, created, user_id, doc_page_count, cmc_reference,
pdf_filename, pdf_created_at, pdf_created_by_user_id,
shipping_details, revision, bill_to, ship_to,
email_sent_at, email_sent_by_user_id
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
-- name: UpdateDocument :exec
UPDATE documents SET
type = ?, user_id = ?, doc_page_count = ?, cmc_reference = ?,
pdf_filename = ?, pdf_created_at = ?, pdf_created_by_user_id = ?,
shipping_details = ?, revision = ?, bill_to = ?, ship_to = ?,
email_sent_at = ?, email_sent_by_user_id = ?
WHERE id = ?;
-- name: DeleteDocument :exec
DELETE FROM documents WHERE id = ?;
-- name: GetRecentDocuments :many
SELECT SELECT
d.id, d.id,
d.type, d.type,
d.created, d.created,
d.user_id,
d.doc_page_count,
d.pdf_filename,
d.pdf_created_at,
d.pdf_created_by_user_id,
d.cmc_reference, d.cmc_reference,
d.pdf_filename,
d.revision, d.revision,
d.email_sent_at, u.username as created_by_username,
d.email_sent_by_user_id, CASE
u.first_name as user_first_name, WHEN d.type = 'quote' THEN CONCAT('Quote ', d.cmc_reference)
u.last_name as user_last_name, WHEN d.type = 'invoice' THEN CONCAT('Invoice ', d.cmc_reference)
u.username as user_username, WHEN d.type = 'purchaseOrder' THEN CONCAT('Purchase Order ', d.cmc_reference)
pdf_creator.first_name as pdf_creator_first_name, WHEN d.type = 'orderAck' THEN CONCAT('Order Ack ', d.cmc_reference)
pdf_creator.last_name as pdf_creator_last_name, WHEN d.type = 'packingList' THEN CONCAT('Packing List ', d.cmc_reference)
pdf_creator.username as pdf_creator_username, ELSE CONCAT(d.type, ' ', d.cmc_reference)
COALESCE(ec.name, ic.name, '') as customer_name, END as display_name
e.title as enquiry_title
FROM documents d FROM documents d
LEFT JOIN users u ON d.user_id = u.id LEFT JOIN users u ON d.user_id = u.id
LEFT JOIN users pdf_creator ON d.pdf_created_by_user_id = pdf_creator.id ORDER BY d.created DESC
LEFT JOIN enquiries e ON d.type IN ('quote', 'orderAck') AND d.cmc_reference = e.title LIMIT ?;
LEFT JOIN customers ec ON e.customer_id = ec.id
LEFT JOIN invoices i ON d.type = 'invoice' AND d.cmc_reference = i.title
LEFT JOIN customers ic ON i.customer_id = ic.id
WHERE d.type = ?
ORDER BY d.id DESC
LIMIT 1000;
-- name: GetDocumentByID :one -- name: CountDocuments :one
SELECT COUNT(*) FROM documents;
-- name: CountDocumentsByType :one
SELECT COUNT(*) FROM documents WHERE type = ?;
-- name: GetDocumentWithUser :one
SELECT SELECT
d.id, d.id,
d.type, d.type,
d.created, d.created,
d.user_id, d.user_id,
d.doc_page_count, d.doc_page_count,
d.cmc_reference,
d.pdf_filename, d.pdf_filename,
d.pdf_created_at, d.pdf_created_at,
d.pdf_created_by_user_id, d.pdf_created_by_user_id,
d.cmc_reference,
d.revision,
d.shipping_details, d.shipping_details,
d.revision,
d.bill_to, d.bill_to,
d.ship_to, d.ship_to,
d.email_sent_at, d.email_sent_at,
d.email_sent_by_user_id, d.email_sent_by_user_id,
u.username as user_username,
u.first_name as user_first_name, u.first_name as user_first_name,
u.last_name as user_last_name, u.last_name as user_last_name,
u.username as user_username, u.email as user_email,
pdf_creator.first_name as pdf_creator_first_name, pu.username as pdf_creator_username,
pdf_creator.last_name as pdf_creator_last_name, pu.first_name as pdf_creator_first_name,
pdf_creator.username as pdf_creator_username, pu.last_name as pdf_creator_last_name,
COALESCE(ec.name, ic.name, '') as customer_name, pu.email as pdf_creator_email
e.title as enquiry_title
FROM documents d FROM documents d
LEFT JOIN users u ON d.user_id = u.id LEFT JOIN users u ON d.user_id = u.id
LEFT JOIN users pdf_creator ON d.pdf_created_by_user_id = pdf_creator.id LEFT JOIN users pu ON d.pdf_created_by_user_id = pu.id
LEFT JOIN enquiries e ON d.type IN ('quote', 'orderAck') AND d.cmc_reference = e.title WHERE d.id = ?;
LEFT JOIN customers ec ON e.customer_id = ec.id
LEFT JOIN invoices i ON d.type = 'invoice' AND d.cmc_reference = i.title
LEFT JOIN customers ic ON i.customer_id = ic.id
WHERE d.id = ?;
-- name: CreateDocument :execresult
INSERT INTO documents (
type,
created,
user_id,
doc_page_count,
cmc_reference,
pdf_filename,
pdf_created_at,
pdf_created_by_user_id,
shipping_details,
revision,
bill_to,
ship_to,
email_sent_at,
email_sent_by_user_id
) VALUES (
?, NOW(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
);
-- name: UpdateDocument :exec
UPDATE documents
SET
type = ?,
user_id = ?,
doc_page_count = ?,
cmc_reference = ?,
pdf_filename = ?,
pdf_created_at = ?,
pdf_created_by_user_id = ?,
shipping_details = ?,
revision = ?,
bill_to = ?,
ship_to = ?,
email_sent_at = ?,
email_sent_by_user_id = ?
WHERE id = ?;
-- name: ArchiveDocument :exec
DELETE FROM documents
WHERE id = ?;
-- name: UnarchiveDocument :exec
-- Note: Unarchiving not supported as documents table doesn't have an archived column
-- This is a no-op for compatibility
SELECT 1 WHERE ? = ?;
-- name: SearchDocuments :many
SELECT
d.id,
d.type,
d.created,
d.user_id,
d.doc_page_count,
d.pdf_filename,
d.pdf_created_at,
d.pdf_created_by_user_id,
d.cmc_reference,
d.revision,
d.email_sent_at,
d.email_sent_by_user_id,
u.first_name as user_first_name,
u.last_name as user_last_name,
u.username as user_username,
pdf_creator.first_name as pdf_creator_first_name,
pdf_creator.last_name as pdf_creator_last_name,
pdf_creator.username as pdf_creator_username,
COALESCE(ec.name, ic.name, '') as customer_name,
e.title as enquiry_title
FROM documents d
LEFT JOIN users u ON d.user_id = u.id
LEFT JOIN users pdf_creator ON d.pdf_created_by_user_id = pdf_creator.id
LEFT JOIN enquiries e ON d.type IN ('quote', 'orderAck') AND d.cmc_reference = e.title
LEFT JOIN customers ec ON e.customer_id = ec.id
LEFT JOIN invoices i ON d.type = 'invoice' AND d.cmc_reference = i.title
LEFT JOIN customers ic ON i.customer_id = ic.id
WHERE (
d.pdf_filename LIKE ? OR
d.cmc_reference LIKE ? OR
COALESCE(ec.name, ic.name) LIKE ? OR
e.title LIKE ? OR
u.username LIKE ?
)
ORDER BY d.id DESC
LIMIT 1000;
-- name: UpdateDocumentPDFInfo :exec
UPDATE documents
SET
pdf_filename = ?,
pdf_created_at = ?,
pdf_created_by_user_id = ?
WHERE id = ?;
-- name: GetPurchaseOrderByDocumentID :one
SELECT
po.id,
po.title,
po.principle_id,
po.principle_reference,
po.issue_date,
po.ordered_from,
po.dispatch_by,
po.deliver_to,
po.shipping_instructions
FROM purchase_orders po
JOIN documents d ON d.cmc_reference = po.title
WHERE d.id = ? AND d.type = 'purchaseOrder';

View file

@ -57,4 +57,9 @@ ORDER BY id DESC;
SELECT * FROM purchase_orders SELECT * FROM purchase_orders
WHERE title LIKE CONCAT('%', ?, '%') WHERE title LIKE CONCAT('%', ?, '%')
ORDER BY issue_date DESC ORDER BY issue_date DESC
LIMIT ? OFFSET ?; LIMIT ? OFFSET ?;
-- name: GetPurchaseOrderByDocumentID :one
SELECT * FROM purchase_orders
WHERE document_id = ?
LIMIT 1;

View file

@ -109,7 +109,8 @@
</div> </div>
{{end}} {{end}}
{{if or .Document.CustomerName .Document.EnquiryTitle.Valid}} {{/* TODO: Add customer and enquiry information when queries are available */}}
{{if false}}
<div class="box"> <div class="box">
<h3 class="title is-5">Related Information</h3> <h3 class="title is-5">Related Information</h3>
<table class="table is-fullwidth"> <table class="table is-fullwidth">