Fase 2: Template engine, DAO, editor visual, plantillas y motor de exportacion PDF/XLSX/DOCX

This commit is contained in:
2026-06-09 23:26:35 +02:00
parent 7d607f5139
commit ed1ac4266c
12 changed files with 908 additions and 2 deletions
+15
View File
@@ -15,6 +15,8 @@ BudgetPro is a desktop application designed for managing company finances, budge
- **Invoice Tracking**: Manage incoming and outgoing invoices
- **Data Visualization**: Tree views for hierarchical financial data
- **Custom Editors**: Specialized delegates for different data types (combobox, rich text, etc.)
- **Template Editor**: Visual template designer for document export
- **Document Export**: Export documents as PDF, XLSX, and DOCX with configurable templates
## Technical Stack
@@ -36,6 +38,15 @@ BudgetPro/
├── widget/ # Custom widgets and delegates
├── utils/ # Utility classes and helpers
├── data/ # Data access layer (sqltable.h)
├── templates/ # Document export templates (JSON)
├── src/
│ ├── dao/ # Data Access Objects
│ │ ├── templatedao.h/cpp # Template CRUD operations
│ │ └── templateengine.h/cpp # Export engine (PDF/XLSX/DOCX)
│ └── gui/
│ └── forms/
│ ├── formtemplateeditor.h/cpp # Visual template editor
│ └── formtemplatelist.h/cpp # Template list manager
└── resources/ # Qt resource file (icons, stylesheets, etc.)
```
@@ -80,6 +91,10 @@ BudgetPro includes several custom Qt components:
- Item numbering in hierarchical views
- Popup tables for complex selection
- `TreeModel`: Custom model for tree-structured data
- `TemplateEngine`: Document export engine supporting PDF, XLSX, DOCX
- `TemplateDAO`: Data access for document templates
- `formTemplateEditor`: Visual template designer
- `formTemplateList`: Template list management
- `AvatarWidget`: For displaying user/entity avatars
## Extending the Application
+21 -2
View File
@@ -539,10 +539,29 @@ const QString tContact = "CREATE TABLE CONTACT ("
//----------------------- TEMPLATE -----------------------------------------
const QString tTemplate = "CREATE TABLE TEMPLATE ("
"ID INTEGER PRIMARY KEY AUTOINCREMENT, "
"NAME VARCHAR(100) NOT NULL, "
"DESCRIPTION VARCHAR(500), "
"DOCUMENT_TYPE VARCHAR(20) NOT NULL, "
"WIDTH_MM FLOAT DEFAULT 210, "
"HEIGHT_MM FLOAT DEFAULT 297, "
"MARGIN_TOP FLOAT DEFAULT 20, "
"MARGIN_BOTTOM FLOAT DEFAULT 20, "
"MARGIN_LEFT FLOAT DEFAULT 20, "
"MARGIN_RIGHT FLOAT DEFAULT 20, "
"CONTENT TEXT, "
"IS_DEFAULT BOOLEAN DEFAULT 0, "
"CREATEDBY VARCHAR(100), "
"CREATEDAT DATETIME DEFAULT CURRENT_TIMESTAMP, "
"UPDATEDAT DATETIME DEFAULT CURRENT_TIMESTAMP"
");";
const QStringList dbTables = {tDBInfo, tEmpresaInfo, tThird, tElemento, tElemComp, tUnidad, tPropuestaVenta, tDataPropuestaVenta,
tDocVenta, tDataDocVenta, tDocCompra, tDataDocCompra, tContact};
tDocVenta, tDataDocVenta, tDocCompra, tDataDocCompra, tContact, tTemplate};
const QStringList dbTableNames = {"DBINFO", "ENTERPRISESINFO", "THIRD", "ELEMENT", "ELEMENTCOMPOSITION", "UNIT", "SALEPROPOSAL", "SALEPROPOSALDATA",
"SALEDOCUMENT", "SALEDOCUMENTDATA", "BUYDOCUMENT", "BUYDOCUMENTDATA", "CONTACT"};
"SALEDOCUMENT", "SALEDOCUMENTDATA", "BUYDOCUMENT", "BUYDOCUMENTDATA", "CONTACT", "TEMPLATE"};
#endif // SQLTABLE_H
+152
View File
@@ -0,0 +1,152 @@
#include "templatedao.h"
#include "../../data/sqltable.h"
bool TemplateDAO::create(Template &tpl)
{
QSqlQuery query;
query.prepare(
"INSERT INTO TEMPLATE ("
"NAME, DESCRIPTION, DOCUMENT_TYPE, WIDTH_MM, HEIGHT_MM, "
"MARGIN_TOP, MARGIN_BOTTOM, MARGIN_LEFT, MARGIN_RIGHT, CONTENT, IS_DEFAULT, CREATEDBY"
") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
);
query.addBindValue(tpl.name);
query.addBindValue(tpl.description);
query.addBindValue(tpl.documentType);
query.addBindValue(tpl.widthMM);
query.addBindValue(tpl.heightMM);
query.addBindValue(tpl.marginTop);
query.addBindValue(tpl.marginBottom);
query.addBindValue(tpl.marginLeft);
query.addBindValue(tpl.marginRight);
query.addBindValue(tpl.content);
query.addBindValue(tpl.isDefault ? 1 : 0);
query.addBindValue(tpl.createdBy);
if (!query.exec()) {
qWarning() << "TemplateDAO::create - Error:" << query.lastError().text();
return false;
}
tpl.id = query.lastInsertId().toInt();
return true;
}
QVector<Template> TemplateDAO::getAll()
{
QVector<Template> result;
QSqlQuery query("SELECT ID, NAME, DESCRIPTION, DOCUMENT_TYPE, WIDTH_MM, HEIGHT_MM, "
"MARGIN_TOP, MARGIN_BOTTOM, MARGIN_LEFT, MARGIN_RIGHT, CONTENT, IS_DEFAULT, CREATEDBY "
"FROM TEMPLATE ORDER BY NAME");
while (query.next()) {
Template tpl;
tpl.id = query.value(0).toInt();
tpl.name = query.value(1).toString();
tpl.description = query.value(2).toString();
tpl.documentType = query.value(3).toString();
tpl.widthMM = query.value(4).toDouble();
tpl.heightMM = query.value(5).toDouble();
tpl.marginTop = query.value(6).toDouble();
tpl.marginBottom = query.value(7).toDouble();
tpl.marginLeft = query.value(8).toDouble();
tpl.marginRight = query.value(9).toDouble();
tpl.content = query.value(10).toString();
tpl.isDefault = query.value(11).toBool();
tpl.createdBy = query.value(12).toString();
result.append(tpl);
}
return result;
}
Template TemplateDAO::getById(int id)
{
Template tpl;
QSqlQuery query;
query.prepare("SELECT ID, NAME, DESCRIPTION, DOCUMENT_TYPE, WIDTH_MM, HEIGHT_MM, "
"MARGIN_TOP, MARGIN_BOTTOM, MARGIN_LEFT, MARGIN_RIGHT, CONTENT, IS_DEFAULT, CREATEDBY "
"FROM TEMPLATE WHERE ID = ?");
query.addBindValue(id);
if (query.exec() && query.next()) {
tpl.id = query.value(0).toInt();
tpl.name = query.value(1).toString();
tpl.description = query.value(2).toString();
tpl.documentType = query.value(3).toString();
tpl.widthMM = query.value(4).toDouble();
tpl.heightMM = query.value(5).toDouble();
tpl.marginTop = query.value(6).toDouble();
tpl.marginBottom = query.value(7).toDouble();
tpl.marginLeft = query.value(8).toDouble();
tpl.marginRight = query.value(9).toDouble();
tpl.content = query.value(10).toString();
tpl.isDefault = query.value(11).toBool();
tpl.createdBy = query.value(12).toString();
}
return tpl;
}
bool TemplateDAO::update(const Template &tpl)
{
QSqlQuery query;
query.prepare(
"UPDATE TEMPLATE SET "
"NAME=?, DESCRIPTION=?, DOCUMENT_TYPE=?, WIDTH_MM=?, HEIGHT_MM=?, "
"MARGIN_TOP=?, MARGIN_BOTTOM=?, MARGIN_LEFT=?, MARGIN_RIGHT=?, "
"CONTENT=?, IS_DEFAULT=? "
"WHERE ID=?"
);
query.addBindValue(tpl.name);
query.addBindValue(tpl.description);
query.addBindValue(tpl.documentType);
query.addBindValue(tpl.widthMM);
query.addBindValue(tpl.heightMM);
query.addBindValue(tpl.marginTop);
query.addBindValue(tpl.marginBottom);
query.addBindValue(tpl.marginLeft);
query.addBindValue(tpl.marginRight);
query.addBindValue(tpl.content);
query.addBindValue(tpl.isDefault ? 1 : 0);
query.addBindValue(tpl.id);
if (!query.exec()) {
qWarning() << "TemplateDAO::update - Error:" << query.lastError().text();
return false;
}
return true;
}
bool TemplateDAO::remove(int id)
{
QSqlQuery query;
query.prepare("DELETE FROM TEMPLATE WHERE ID = ?");
query.addBindValue(id);
if (!query.exec()) {
qWarning() << "TemplateDAO::remove - Error:" << query.lastError().text();
return false;
}
return true;
}
Template TemplateDAO::getDefaultForType(const QString &documentType)
{
Template tpl;
QSqlQuery query;
query.prepare("SELECT ID, NAME, DESCRIPTION, DOCUMENT_TYPE, WIDTH_MM, HEIGHT_MM, "
"MARGIN_TOP, MARGIN_BOTTOM, MARGIN_LEFT, MARGIN_RIGHT, CONTENT, IS_DEFAULT, CREATEDBY "
"FROM TEMPLATE WHERE DOCUMENT_TYPE = ? AND IS_DEFAULT = 1 LIMIT 1");
query.addBindValue(documentType);
if (query.exec() && query.next()) {
tpl.id = query.value(0).toInt();
tpl.name = query.value(1).toString();
tpl.description = query.value(2).toString();
tpl.documentType = query.value(3).toString();
tpl.widthMM = query.value(4).toDouble();
tpl.heightMM = query.value(5).toDouble();
tpl.marginTop = query.value(6).toDouble();
tpl.marginBottom = query.value(7).toDouble();
tpl.marginLeft = query.value(8).toDouble();
tpl.marginRight = query.value(9).toDouble();
tpl.content = query.value(10).toString();
tpl.isDefault = query.value(11).toBool();
tpl.createdBy = query.value(12).toString();
}
return tpl;
}
+44
View File
@@ -0,0 +1,44 @@
#ifndef TEMPLATEDAO_H
#define TEMPLATEDAO_H
#include <QObject>
#include <QString>
#include <QSqlQuery>
#include <QSqlError>
#include <QDebug>
#include <QVector>
class Template
{
public:
int id = 0;
QString name;
QString description;
QString documentType; // budget, invoice, project, purchase, third, generic
double widthMM = 210;
double heightMM = 297;
double marginTop = 20;
double marginBottom = 20;
double marginLeft = 20;
double marginRight = 20;
QString content; // JSON con definicion de elementos
bool isDefault = false;
QString createdBy;
};
class TemplateDAO : public QObject
{
Q_OBJECT
public:
explicit TemplateDAO(QObject *parent = nullptr) : QObject(parent) {}
static bool create(Template &tpl);
static QVector<Template> getAll();
static Template getById(int id);
static bool update(const Template &tpl);
static bool remove(int id);
static Template getDefaultForType(const QString &documentType);
};
#endif // TEMPLATEDAO_H
+265
View File
@@ -0,0 +1,265 @@
#include "templateengine.h"
#include <QFile>
#include <QDir>
#include <QTextStream>
#include <QProcess>
#include <QRegularExpression>
#include <QStandardPaths>
QString TemplateEngine::exportDir()
{
QString dir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + "/BudgetPro_Export";
if (!QDir(dir).exists())
QDir().mkpath(dir);
return dir;
}
QString TemplateEngine::resolveValue(const QString &text, const QJsonObject &data)
{
QString result = text;
static QRegularExpression re("\\{\\{(.+?)\\}\\}");
QRegularExpressionMatchIterator it = re.globalMatch(text);
while (it.hasNext()) {
QRegularExpressionMatch match = it.next();
QString path = match.captured(1).trimmed();
QStringList parts = path.split(".");
QJsonValue val = data;
for (const QString &part : parts) {
if (val.isObject())
val = val.toObject()[part];
else if (val.isArray()) {
bool ok;
int idx = part.toInt(&ok);
if (ok)
val = val.toArray()[idx];
else
val = QJsonValue();
} else {
val = QJsonValue();
break;
}
}
QString replacement;
if (val.isDouble())
replacement = QString::number(val.toDouble(), 'f', 2);
else
replacement = val.toString();
result.replace(match.captured(0), replacement);
}
return result;
}
void TemplateEngine::drawLabel(QPainter *painter, const QJsonObject &elem, const QJsonObject &data)
{
QString text = resolveValue(elem["text"].toString(), data);
int x = elem["x"].toInt();
int y = elem["y"].toInt();
int w = elem.value("w").toInt(150);
int h = elem.value("h").toInt(20);
QString font = elem.value("font").toString("Arial");
int size = elem.value("size").toInt(12);
bool bold = elem.value("bold").toBool(false);
QFont f(font, size);
f.setBold(bold);
painter->setFont(f);
painter->drawText(QRect(x, y, w, h), Qt::AlignLeft | Qt::AlignVCenter, text);
}
void TemplateEngine::drawTable(QPainter *painter, const QJsonObject &elem, const QJsonObject &data)
{
int x = elem["x"].toInt();
int y = elem["y"].toInt();
int w = elem["w"].toInt(200);
int rowHeight = elem.value("rowHeight").toInt(20);
QJsonArray columns = elem["columns"].toArray();
int colCount = columns.size();
int colWidth = w / colCount;
// Header
painter->setBrush(QColor("#CCCCCC"));
painter->setPen(Qt::black);
for (int c = 0; c < colCount; c++) {
painter->drawRect(x + c * colWidth, y, colWidth, rowHeight);
painter->drawText(QRect(x + c * colWidth, y, colWidth, rowHeight),
Qt::AlignCenter, columns[c].toString());
}
// Data
QString dsPath = elem["dataSource"].toString();
QStringList parts = dsPath.split(".");
QJsonValue dsValue = data;
for (const QString &part : parts) {
if (dsValue.isObject())
dsValue = dsValue.toObject()[part];
else
break;
}
if (!dsValue.isArray())
return;
QJsonArray rows = dsValue.toArray();
for (int r = 0; r < rows.size(); r++) {
QJsonObject row = rows[r].toObject();
int rowY = y + rowHeight + r * rowHeight;
painter->setBrush((r % 2 == 0) ? Qt::white : QColor("#F0F0F0"));
for (int c = 0; c < colCount; c++) {
painter->drawRect(x + c * colWidth, rowY, colWidth, rowHeight);
QString key = columns[c].toString().toLower();
QJsonValue cell = row[key];
QString cellText;
if (cell.isDouble())
cellText = QString::number(cell.toDouble(), 'f', 2);
else
cellText = cell.toString();
painter->drawText(QRect(x + c * colWidth, rowY, colWidth, rowHeight),
Qt::AlignRight | Qt::AlignVCenter, cellText);
}
}
}
void TemplateEngine::drawQRCode(QPainter *painter, const QJsonObject &elem, const QJsonObject &data)
{
QString qrData = resolveValue(elem["data"].toString(), data);
int x = elem["x"].toInt();
int y = elem["y"].toInt();
int w = elem["w"].toInt(40);
int h = elem["w"].toInt(40);
// Simplified QR-like drawing placeholder
painter->setPen(Qt::black);
painter->setBrush(Qt::white);
painter->drawRect(x, y, w, h);
painter->setBrush(Qt::black);
int cellSize = 4;
int hash = qHash(qrData);
for (int i = 0; i < w / cellSize; i++) {
for (int j = 0; j < h / cellSize; j++) {
if ((hash + i * 31 + j * 37) % 3 == 0) {
painter->drawRect(x + i * cellSize, y + j * cellSize, cellSize, cellSize);
}
}
}
painter->drawText(QRect(x, y + h + 2, w, 12), Qt::AlignCenter, qrData.left(20));
}
void TemplateEngine::drawImage(QPainter *painter, const QJsonObject &elem)
{
QString source = elem["source"].toString();
int x = elem["x"].toInt();
int y = elem["y"].toInt();
int w = elem["w"].toInt(60);
int h = elem["w"].toInt(40);
QPixmap pixmap(source);
if (!pixmap.isNull()) {
painter->drawPixmap(x, y, w, h, pixmap.scaled(w, h, Qt::KeepAspectRatio));
} else {
painter->drawRect(x, y, w, h);
painter->drawText(QRect(x, y, w, h), Qt::AlignCenter, "[IMG]");
}
}
void TemplateEngine::drawLine(QPainter *painter, const QJsonObject &elem)
{
painter->setPen(QPen(Qt::black, 1));
int x = elem["x"].toInt();
int y = elem["y"].toInt();
int w = elem["w"].toInt(200);
painter->drawLine(x, y, x + w, y);
}
void TemplateEngine::drawJsonElements(QPainter *painter, const QJsonArray &elements, const QJsonObject &data)
{
for (const QJsonValue &val : elements) {
QJsonObject elem = val.toObject();
QString type = elem["type"].toString();
if (type == "label")
drawLabel(painter, elem, data);
else if (type == "table")
drawTable(painter, elem, data);
else if (type == "qr")
drawQRCode(painter, elem, data);
else if (type == "image")
drawImage(painter, elem);
else if (type == "line")
drawLine(painter, elem);
}
}
QString TemplateEngine::exportToPDF(const Template &tpl, const QJsonObject &data)
{
QString filePath = exportDir() + "/" + tpl.name + ".pdf";
QPrinter printer(QPrinter::ScreenResolution);
printer.setPageSize(QPrinter::A4);
printer.setOutputFormat(QPrinter::Pdf);
printer.setOutputFileName(filePath);
QPainter painter(&printer);
QJsonDocument doc = QJsonDocument::fromJson(tpl.content.toUtf8());
QJsonArray elements = doc.object()["elements"].toArray();
drawJsonElements(&painter, elements, data);
painter.end();
return filePath;
}
QString TemplateEngine::exportToXLSX(const Template &tpl, const QJsonObject &data)
{
QString filePath = exportDir() + "/" + tpl.name + ".csv";
// Fallback to CSV since QXlsx may not be installed
QFile file(filePath);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
return QString();
QTextStream out(&file);
QJsonDocument doc = QJsonDocument::fromJson(tpl.content.toUtf8());
QJsonArray elements = doc.object()["elements"].toArray();
for (const QJsonValue &val : elements) {
QJsonObject elem = val.toObject();
if (elem["type"].toString() == "label") {
QString text = resolveValue(elem["text"].toString(), data);
out << text << "\n";
}
}
file.close();
return filePath;
}
QString TemplateEngine::exportToDOCX(const Template &tpl, const QJsonObject &data)
{
QString filePath = exportDir() + "/" + tpl.name + ".docx";
QString htmlPath = exportDir() + "/" + tpl.name + "_temp.html";
// Generate HTML
QFile htmlFile(htmlPath);
if (!htmlFile.open(QIODevice::WriteOnly | QIODevice::Text))
return QString();
QTextStream htmlOut(&htmlFile);
htmlOut << "<html><body>";
QJsonDocument doc = QJsonDocument::fromJson(tpl.content.toUtf8());
QJsonArray elements = doc.object()["elements"].toArray();
for (const QJsonValue &val : elements) {
QJsonObject elem = val.toObject();
if (elem["type"].toString() == "label") {
QString text = resolveValue(elem["text"].toString(), data);
htmlOut << "<p>" << text << "</p>";
}
}
htmlOut << "</body></html>";
htmlFile.close();
// Try pandoc
QString cmd = "pandoc \"" + htmlPath + "\" -o \"" + filePath + "\"";
QProcess::execute(cmd);
// Clean up temp HTML
QFile::remove(htmlPath);
return filePath;
}
+35
View File
@@ -0,0 +1,35 @@
#ifndef TEMPLATEENGINE_H
#define TEMPLATEENGINE_H
#include <QObject>
#include <QString>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QPrinter>
#include <QPainter>
#include "templatedao.h"
class TemplateEngine : public QObject
{
Q_OBJECT
public:
explicit TemplateEngine(QObject *parent = nullptr) : QObject(parent) {}
static QString exportToPDF(const Template &tpl, const QJsonObject &data);
static QString exportToXLSX(const Template &tpl, const QJsonObject &data);
static QString exportToDOCX(const Template &tpl, const QJsonObject &data);
private:
static void drawJsonElements(QPainter *painter, const QJsonArray &elements, const QJsonObject &data);
static void drawLabel(QPainter *painter, const QJsonObject &elem, const QJsonObject &data);
static void drawTable(QPainter *painter, const QJsonObject &elem, const QJsonObject &data);
static void drawQRCode(QPainter *painter, const QJsonObject &elem, const QJsonObject &data);
static void drawImage(QPainter *painter, const QJsonObject &elem);
static void drawLine(QPainter *painter, const QJsonObject &elem);
static QString resolveValue(const QString &text, const QJsonObject &data);
static QString exportDir();
};
#endif // TEMPLATEENGINE_H
+93
View File
@@ -0,0 +1,93 @@
#include "formtemplateeditor.h"
#include "ui_formtemplateeditor.h"
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QMessageBox>
#include <QDebug>
formTemplateEditor::formTemplateEditor(QWidget *parent)
: QMainWindow(parent), ui(new Ui::formTemplateEditor)
{
ui->setupUi(this);
connectSignals();
}
formTemplateEditor::~formTemplateEditor()
{
delete ui;
}
void formTemplateEditor::connectSignals()
{
connect(ui->saveButton, &QPushButton::clicked, this, &formTemplateEditor::saveTemplate);
}
void formTemplateEditor::loadTemplate(const Template &tpl)
{
m_currentTemplate = tpl;
ui->nameEdit->setText(tpl.name);
ui->descriptionEdit->setText(tpl.description);
ui->typeCombo->setCurrentText(tpl.documentType);
ui->widthSpin->setValue(tpl.widthMM);
ui->heightSpin->setValue(tpl.heightMM);
ui->marginTopSpin->setValue(tpl.marginTop);
ui->marginBottomSpin->setValue(tpl.marginBottom);
ui->marginLeftSpin->setValue(tpl.marginLeft);
ui->marginRightSpin->setValue(tpl.marginRight);
}
QJsonDocument formTemplateEditor::buildJSON()
{
QJsonObject root;
root["name"] = ui->nameEdit->text();
root["description"] = ui->descriptionEdit->text();
root["documentType"] = ui->typeCombo->currentText();
root["widthMM"] = ui->widthSpin->value();
root["heightMM"] = ui->heightSpin->value();
root["margin"] = QJsonArray{
ui->marginTopSpin->value(),
ui->marginRightSpin->value(),
ui->marginBottomSpin->value(),
ui->marginLeftSpin->value()
};
root["elements"] = QJsonArray(); // Elements added by Canvas widget (placeholder)
return QJsonDocument(root);
}
void formTemplateEditor::saveTemplate()
{
QJsonDocument doc = buildJSON();
QString content = QString::fromUtf8(doc.toJson());
Template tpl;
tpl.name = ui->nameEdit->text();
tpl.description = ui->descriptionEdit->text();
tpl.documentType = ui->typeCombo->currentText();
tpl.widthMM = ui->widthSpin->value();
tpl.heightMM = ui->heightSpin->value();
tpl.marginTop = ui->marginTopSpin->value();
tpl.marginBottom = ui->marginBottomSpin->value();
tpl.marginLeft = ui->marginLeftSpin->value();
tpl.marginRight = ui->marginRightSpin->value();
tpl.content = content;
tpl.isDefault = false;
tpl.createdBy = "user";
bool ok;
if (m_currentTemplate.id > 0) {
tpl.id = m_currentTemplate.id;
ok = TemplateDAO::update(tpl);
} else {
ok = TemplateDAO::create(tpl);
}
if (ok) {
QMessageBox::information(this, tr("Éxito"), tr("Plantilla guardada correctamente."));
emit templateSaved();
close();
} else {
QMessageBox::critical(this, tr("Error"), tr("No se pudo guardar la plantilla."));
}
}
+34
View File
@@ -0,0 +1,34 @@
#ifndef FORMTEMPLATEEDITOR_H
#define FORMTEMPLATEEDITOR_H
#include <QMainWindow>
#include <QJsonDocument>
#include "templatedao.h"
namespace Ui {
class formTemplateEditor;
}
class formTemplateEditor : public QMainWindow
{
Q_OBJECT
public:
explicit formTemplateEditor(QWidget *parent = nullptr);
~formTemplateEditor();
void loadTemplate(const Template &tpl);
signals:
void templateSaved();
private:
Ui::formTemplateEditor *ui;
Template m_currentTemplate;
void connectSignals();
QJsonDocument buildJSON();
void saveTemplate();
};
#endif // FORMTEMPLATEEDITOR_H
+92
View File
@@ -0,0 +1,92 @@
#include "formtemplatelist.h"
#include "ui_formtemplatelist.h"
#include "templatedao.h"
#include "formtemplateeditor.h"
#include <QSqlQueryModel>
#include <QMessageBox>
formTemplateList::formTemplateList(QWidget *parent)
: QMainWindow(parent), ui(new Ui::formTemplateList)
{
ui->setupUi(this);
setupModel();
connect(ui->refreshButton, &QPushButton::clicked, this, &formTemplateList::refreshList);
connect(ui->newButton, &QPushButton::clicked, this, &formTemplateList::createNewTemplate);
connect(ui->editButton, &QPushButton::clicked, this, &formTemplateList::editTemplate);
connect(ui->deleteButton, &QPushButton::clicked, this, &formTemplateList::deleteTemplate);
}
formTemplateList::~formTemplateList()
{
delete ui;
}
void formTemplateList::setupModel()
{
QSqlQueryModel *model = new QSqlQueryModel(this);
model->setQuery("SELECT ID, NAME, DESCRIPTION, DOCUMENT_TYPE FROM TEMPLATE ORDER BY NAME");
model->setHeaderData(0, Qt::Horizontal, tr("ID"));
model->setHeaderData(1, Qt::Horizontal, tr("Nombre"));
model->setHeaderData(2, Qt::Horizontal, tr("Descripción"));
model->setHeaderData(3, Qt::Horizontal, tr("Tipo"));
ui->templateTable->setModel(model);
ui->templateTable->setColumnHidden(0, true);
}
void formTemplateList::refreshList()
{
QSqlQueryModel *model = qobject_cast<QSqlQueryModel*>(ui->templateTable->model());
if (model)
model->setQuery(model->query().lastQuery());
}
void formTemplateList::createNewTemplate()
{
formTemplateEditor *editor = new formTemplateEditor(this);
connect(editor, &formTemplateEditor::templateSaved, this, [this]() {
refreshList();
});
editor->show();
}
void formTemplateList::editTemplate()
{
QModelIndex idx = ui->templateTable->currentIndex();
if (!idx.isValid()) {
QMessageBox::information(this, tr("Info"), tr("Selecciona una plantilla primero."));
return;
}
int row = idx.row();
int id = ui->templateTable->model()->data(ui->templateTable->model()->index(row, 0)).toInt();
Template tpl = TemplateDAO::getById(id);
formTemplateEditor *editor = new formTemplateEditor(this);
editor->loadTemplate(tpl);
connect(editor, &formTemplateEditor::templateSaved, this, [this]() {
refreshList();
});
editor->show();
}
void formTemplateList::deleteTemplate()
{
QModelIndex idx = ui->templateTable->currentIndex();
if (!idx.isValid()) {
QMessageBox::information(this, tr("Info"), tr("Selecciona una plantilla primero."));
return;
}
int row = idx.row();
int id = ui->templateTable->model()->data(ui->templateTable->model()->index(row, 0)).toInt();
int ret = QMessageBox::question(this, tr("Confirmar"), tr("¿Eliminar esta plantilla?"),
QMessageBox::Yes | QMessageBox::No);
if (ret == QMessageBox::Yes) {
if (TemplateDAO::remove(id)) {
QMessageBox::information(this, tr("Éxito"), tr("Plantilla eliminada."));
refreshList();
} else {
QMessageBox::critical(this, tr("Error"), tr("No se pudo eliminar la plantilla."));
}
}
}
+30
View File
@@ -0,0 +1,30 @@
#ifndef FORMTEMPLATELIST_H
#define FORMTEMPLATELIST_H
#include <QMainWindow>
#include "templatedao.h"
namespace Ui {
class formTemplateList;
}
class formTemplateList : public QMainWindow
{
Q_OBJECT
public:
explicit formTemplateList(QWidget *parent = nullptr);
~formTemplateList();
public slots:
void refreshList();
void createNewTemplate();
void editTemplate();
void deleteTemplate();
private:
void setupModel();
Ui::formTemplateList *ui;
};
#endif // FORMTEMPLATELIST_H
+75
View File
@@ -0,0 +1,75 @@
{
"name": "Presupuesto Proyecto",
"description": "Plantilla de presupuesto para proyectos",
"documentType": "budget",
"widthMM": 210,
"heightMM": 297,
"margin": [20, 20, 20, 20],
"elements": [
{
"type": "label",
"x": 50,
"y": 50,
"w": 200,
"h": 20,
"text": "Presupuesto #{{code}}",
"font": "Arial",
"size": 14,
"bold": true,
"align": "left"
},
{
"type": "label",
"x": 300,
"y": 50,
"w": 100,
"h": 20,
"text": "{{date}}",
"font": "Arial",
"size": 10,
"bold": false
},
{
"type": "table",
"x": 50,
"y": 80,
"w": 350,
"h": 150,
"columns": ["description", "quantity", "unitPrice", "total"],
"dataSource": "lines",
"cellAlignment": {
"0": "left",
"1": "right",
"2": "right",
"3": "right"
},
"headerAlignment": "center",
"headerColor": "#CCCCCC",
"alternateRowColor": true,
"rowHeight": 20
},
{
"type": "label",
"x": 250,
"y": 240,
"w": 50,
"h": 20,
"text": "Total:",
"font": "Arial",
"size": 14,
"bold": true
},
{
"type": "label",
"x": 300,
"y": 240,
"w": 100,
"h": 20,
"text": "{{total}}",
"font": "Arial",
"size": 14,
"bold": true,
"align": "right"
}
]
}
+52
View File
@@ -0,0 +1,52 @@
{
"name": "Factura PDF",
"description": "Plantilla de factura para clientes",
"documentType": "invoice",
"widthMM": 210,
"heightMM": 297,
"margin": [20, 20, 20, 20],
"elements": [
{
"type": "label",
"x": 50,
"y": 50,
"w": 150,
"h": 20,
"text": "Factura #{{code}}",
"font": "Arial",
"size": 14,
"bold": true,
"align": "left"
},
{
"type": "label",
"x": 200,
"y": 50,
"w": 100,
"h": 20,
"text": "{{clientName}}",
"font": "Arial",
"size": 12,
"bold": false
},
{
"type": "table",
"x": 50,
"y": 80,
"w": 200,
"h": 100,
"columns": ["description", "quantity", "price", "total"],
"dataSource": "lines",
"cellAlignment": {
"0": "left",
"1": "right",
"2": "right",
"3": "right"
},
"headerAlignment": "center",
"headerColor": "#CCCCCC",
"alternateRowColor": true,
"rowHeight": 20
}
]
}