mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-23 15:34:12 +02:00
Merge pull request #830 from enesgursoy6110/feature/smart-device-bom
Add smart device metadata to the existing BOM export
This commit is contained in:
@@ -134,6 +134,7 @@ include(cmake/fetch_kdeaddons.cmake)
|
|||||||
option(PACKAGE_TESTS "Build the tests" ON)
|
option(PACKAGE_TESTS "Build the tests" ON)
|
||||||
if(PACKAGE_TESTS)
|
if(PACKAGE_TESTS)
|
||||||
message("Add sub directory tests")
|
message("Add sub directory tests")
|
||||||
|
enable_testing()
|
||||||
add_subdirectory(tests)
|
add_subdirectory(tests)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|||||||
@@ -177,6 +177,8 @@ set(QET_SRC_FILES
|
|||||||
${QET_DIR}/sources/ui/wiringlistdialog.h
|
${QET_DIR}/sources/ui/wiringlistdialog.h
|
||||||
${QET_DIR}/sources/ui/wiringlistdialog.cpp
|
${QET_DIR}/sources/ui/wiringlistdialog.cpp
|
||||||
${QET_DIR}/sources/conductornumexport.h
|
${QET_DIR}/sources/conductornumexport.h
|
||||||
|
${QET_DIR}/sources/bomexport.cpp
|
||||||
|
${QET_DIR}/sources/bomexport.h
|
||||||
${QET_DIR}/sources/conductorprofile.cpp
|
${QET_DIR}/sources/conductorprofile.cpp
|
||||||
${QET_DIR}/sources/conductorprofile.h
|
${QET_DIR}/sources/conductorprofile.h
|
||||||
${QET_DIR}/sources/conductorproperties.cpp
|
${QET_DIR}/sources/conductorproperties.cpp
|
||||||
@@ -475,6 +477,8 @@ set(QET_SRC_FILES
|
|||||||
${QET_DIR}/sources/project/projectusagetracker.cpp
|
${QET_DIR}/sources/project/projectusagetracker.cpp
|
||||||
${QET_DIR}/sources/project/projectusagetracker.h
|
${QET_DIR}/sources/project/projectusagetracker.h
|
||||||
|
|
||||||
|
${QET_DIR}/sources/properties/deviceinformation.cpp
|
||||||
|
${QET_DIR}/sources/properties/deviceinformation.h
|
||||||
${QET_DIR}/sources/properties/elementdata.cpp
|
${QET_DIR}/sources/properties/elementdata.cpp
|
||||||
${QET_DIR}/sources/properties/elementdata.h
|
${QET_DIR}/sources/properties/elementdata.h
|
||||||
${QET_DIR}/sources/properties/propertiesinterface.cpp
|
${QET_DIR}/sources/properties/propertiesinterface.cpp
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
# Smart Device Information and BOM CSV
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
- A `.qet` project is XML. Placed component properties are held in
|
||||||
|
`ElementData::m_informations`, a `DiagramContext`, and are serialized by the
|
||||||
|
existing `Element::fromXml()` and `Element::toXml()` path.
|
||||||
|
- `DeviceInformation` is a typed view over that existing context. It does not
|
||||||
|
introduce a second data store. Updates made by the properties editor continue
|
||||||
|
to use `ChangeElementInformationCommand`, including undo/redo and dirty state.
|
||||||
|
- The project database copies element contexts into `element_info` and exposes
|
||||||
|
reporting fields through `element_nomenclature_view`.
|
||||||
|
- GUI and CLI BOM exports both use `BomExport::defaultQuery()` against that
|
||||||
|
view. The existing `BOMExportDialog` remains the only GUI export entry point;
|
||||||
|
users can still customize columns, filters, and grouped/ungrouped output.
|
||||||
|
|
||||||
|
## Device Properties
|
||||||
|
|
||||||
|
| Device field | XML/context key | Default BOM |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Device tag | `label` | Yes |
|
||||||
|
| Manufacturer | `manufacturer` | Yes |
|
||||||
|
| Part number | `manufacturer_reference` | Yes |
|
||||||
|
| Model | `model` | Yes |
|
||||||
|
| Description | `description` | Yes |
|
||||||
|
| Category | `category` | Yes |
|
||||||
|
| Quantity | `quantity` | Yes |
|
||||||
|
| Voltage rating | `voltage_rating` | Yes |
|
||||||
|
| Current rating | `current_rating` | Yes |
|
||||||
|
| Page | `folio` from the diagram | Yes |
|
||||||
|
| Notes | `notes` | Yes |
|
||||||
|
|
||||||
|
The five new context keys are `model`, `category`, `voltage_rating`,
|
||||||
|
`current_rating`, and `notes`. They are available in the existing information
|
||||||
|
editor for Simple and Master elements, in symbol definitions, in the SQL
|
||||||
|
nomenclature view, and in the export column picker.
|
||||||
|
|
||||||
|
No XML version bump or migration is required. Missing keys read as empty
|
||||||
|
strings, empty properties are omitted by the existing writer, and unknown
|
||||||
|
context keys remain preserved. Quantity and ratings stay free text so existing
|
||||||
|
unit conventions and decimal quantities are not changed implicitly.
|
||||||
|
|
||||||
|
## BOM Policy
|
||||||
|
|
||||||
|
The default BOM query exports one row per Simple or Master element. Slave
|
||||||
|
contacts, terminal-strip elements, reports, thumbnails, and conductor
|
||||||
|
definitions are excluded. A Master device is counted once and PLC I/O entries
|
||||||
|
are not expanded. The existing `exclude_from_bom` property is honored by the
|
||||||
|
SQL view, including common true representations (`true`, `1`, `yes`, `on`).
|
||||||
|
|
||||||
|
The default columns are:
|
||||||
|
|
||||||
|
`Device tag, Designation, Manufacturer, Part number, Model, Description,
|
||||||
|
Category, Quantity, Voltage rating, Current rating, Page, Notes`.
|
||||||
|
|
||||||
|
The existing **Format as bill of materials** checkbox provides grouped output;
|
||||||
|
it is off by default so the initial export remains ungrouped. Users may change
|
||||||
|
the query and column order through the existing dialog. The command-line
|
||||||
|
`--export-bom` operation uses the same default query and column order.
|
||||||
|
|
||||||
|
CSV output is UTF-8 with a BOM, semicolon-delimited, and quotes every field.
|
||||||
|
Embedded quotes are doubled and embedded newlines are retained. `QSaveFile`
|
||||||
|
provides atomic replacement; query, open, write, and commit failures are
|
||||||
|
reported without deleting an existing destination first.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
The smart-device test is registered explicitly in
|
||||||
|
`tests/qttest/CMakeLists.txt` and uses only Qt Test and Qt SQL. It is therefore
|
||||||
|
part of the normal cross-platform CMake/CTest flow:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cmake -S . -B build -DPACKAGE_TESTS=ON
|
||||||
|
cmake --build build
|
||||||
|
ctest --test-dir build --output-on-failure
|
||||||
|
```
|
||||||
|
|
||||||
|
`tst_smart_device` checks the metadata column contract, Simple/Master filtering,
|
||||||
|
stable page ordering, UTF-8 with Latin and non-Latin samples, CSV escaping, and
|
||||||
|
atomic write success/failure. Existing project XML save/load tests cover the
|
||||||
|
generic `DiagramContext` persistence path used by the new keys.
|
||||||
|
|
||||||
|
Manual checks:
|
||||||
|
|
||||||
|
1. Open an existing project and edit a Simple or Master element's information.
|
||||||
|
2. Set all device fields, apply, undo, redo, save, close, and reopen.
|
||||||
|
3. Open Project > Export as CSV and verify the device columns are preselected.
|
||||||
|
4. Export with grouping both disabled and enabled.
|
||||||
|
5. Verify leading-zero part numbers, non-Latin text, quotes, and newlines in a
|
||||||
|
UTF-8 CSV reader.
|
||||||
|
|
||||||
|
Cable management, terminal-strip planning, automatic wire numbering, PLC I/O
|
||||||
|
expansion, panel layout, and manufacturer catalog integration are out of scope.
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2006-2026 The QElectroTech Team
|
||||||
|
This file is part of QElectroTech.
|
||||||
|
|
||||||
|
QElectroTech is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
QElectroTech is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#include "bomexport.h"
|
||||||
|
|
||||||
|
#include <QSaveFile>
|
||||||
|
#include <QSqlQuery>
|
||||||
|
#include <QSqlRecord>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
QString csvField(QString value)
|
||||||
|
{
|
||||||
|
value.replace(QLatin1Char('"'), QStringLiteral("\"\""));
|
||||||
|
return QLatin1Char('"') + value + QLatin1Char('"');
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray csvRecord(const QStringList &values)
|
||||||
|
{
|
||||||
|
QStringList escaped;
|
||||||
|
for (const auto &value : values) {
|
||||||
|
escaped.append(csvField(value));
|
||||||
|
}
|
||||||
|
return (escaped.join(QLatin1Char(';')) + QLatin1Char('\n')).toUtf8();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList BomExport::defaultColumns()
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
QStringLiteral("label"),
|
||||||
|
QStringLiteral("designation"),
|
||||||
|
QStringLiteral("manufacturer"),
|
||||||
|
QStringLiteral("manufacturer_reference"),
|
||||||
|
QStringLiteral("model"),
|
||||||
|
QStringLiteral("description"),
|
||||||
|
QStringLiteral("category"),
|
||||||
|
QStringLiteral("quantity"),
|
||||||
|
QStringLiteral("voltage_rating"),
|
||||||
|
QStringLiteral("current_rating"),
|
||||||
|
QStringLiteral("folio"),
|
||||||
|
QStringLiteral("notes")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
QString BomExport::defaultQuery()
|
||||||
|
{
|
||||||
|
return QStringLiteral("SELECT %1 FROM element_nomenclature_view "
|
||||||
|
"WHERE ( element_type = 'simple' OR element_type = 'master') "
|
||||||
|
"ORDER BY diagram_position, position, label")
|
||||||
|
.arg(defaultColumns().join(QStringLiteral(", ")));
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray BomExport::toCsv(QSqlQuery &query, const QStringList &headers,
|
||||||
|
bool include_headers, int *row_count)
|
||||||
|
{
|
||||||
|
QByteArray csv("\xEF\xBB\xBF");
|
||||||
|
if (include_headers) {
|
||||||
|
csv += csvRecord(headers);
|
||||||
|
}
|
||||||
|
int rows = 0;
|
||||||
|
while (query.next())
|
||||||
|
{
|
||||||
|
QStringList values;
|
||||||
|
for (int i = 0; i < query.record().count(); ++i) {
|
||||||
|
values.append(query.value(i).toString());
|
||||||
|
}
|
||||||
|
csv += csvRecord(values);
|
||||||
|
++rows;
|
||||||
|
}
|
||||||
|
if (row_count) {
|
||||||
|
*row_count = rows;
|
||||||
|
}
|
||||||
|
return csv;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BomExport::writeCsv(const QString &file_path, const QByteArray &csv,
|
||||||
|
QString *error)
|
||||||
|
{
|
||||||
|
if (error) {
|
||||||
|
error->clear();
|
||||||
|
}
|
||||||
|
QSaveFile file(file_path);
|
||||||
|
if (!file.open(QIODevice::WriteOnly) || file.write(csv) != csv.size())
|
||||||
|
{
|
||||||
|
if (error) {
|
||||||
|
*error = file.errorString();
|
||||||
|
}
|
||||||
|
file.cancelWriting();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!file.commit())
|
||||||
|
{
|
||||||
|
if (error) {
|
||||||
|
*error = file.errorString();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2006-2026 The QElectroTech Team
|
||||||
|
This file is part of QElectroTech.
|
||||||
|
|
||||||
|
QElectroTech is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
QElectroTech is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#ifndef BOMEXPORT_H
|
||||||
|
#define BOMEXPORT_H
|
||||||
|
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QString>
|
||||||
|
#include <QStringList>
|
||||||
|
|
||||||
|
class QSqlQuery;
|
||||||
|
|
||||||
|
namespace BomExport
|
||||||
|
{
|
||||||
|
QStringList defaultColumns();
|
||||||
|
QString defaultQuery();
|
||||||
|
QByteArray toCsv(QSqlQuery &query, const QStringList &headers,
|
||||||
|
bool include_headers = true, int *row_count = nullptr);
|
||||||
|
bool writeCsv(const QString &file_path, const QByteArray &csv,
|
||||||
|
QString *error = nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // BOMEXPORT_H
|
||||||
+21
-41
@@ -16,6 +16,7 @@
|
|||||||
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
#include "cli_export.h"
|
#include "cli_export.h"
|
||||||
|
#include "bomexport.h"
|
||||||
|
|
||||||
#include "bordertitleblock.h"
|
#include "bordertitleblock.h"
|
||||||
#include "conductornumexport.h"
|
#include "conductornumexport.h"
|
||||||
@@ -304,61 +305,40 @@ int exportCsv(QETProject &project, const QString &format, const QString &output)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Quote a field for CSV output (RFC-4180 style, ';' delimiter).
|
/// Bill of materials from the same project database and default query as the
|
||||||
QString csvField(const QString &value)
|
/// GUI nomenclature export.
|
||||||
{
|
|
||||||
if (value.contains(';') || value.contains('"')
|
|
||||||
|| value.contains('\n') || value.contains('\r')) {
|
|
||||||
QString v = value;
|
|
||||||
v.replace('"', "\"\"");
|
|
||||||
return '"' % v % '"';
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Bill of materials: one row per element, key component-data fields.
|
|
||||||
/// Pulls from QET's own project database (the same source as the GUI BOM
|
|
||||||
/// export), so the output matches what the editor produces.
|
|
||||||
int exportBom(QETProject &project, const QString &output)
|
int exportBom(QETProject &project, const QString &output)
|
||||||
{
|
{
|
||||||
// The project database is built lazily; force a (re)build before querying.
|
|
||||||
project.dataBase()->updateDB();
|
project.dataBase()->updateDB();
|
||||||
|
QSqlQuery query = project.dataBase()->newQuery(BomExport::defaultQuery());
|
||||||
static const QStringList columns {
|
|
||||||
"label", "designation", "manufacturer", "manufacturer_reference",
|
|
||||||
"quantity", "location", "function", "title", "folio"
|
|
||||||
};
|
|
||||||
|
|
||||||
QSqlQuery query = project.dataBase()->newQuery(
|
|
||||||
"SELECT " % columns.join(", ") %
|
|
||||||
" FROM element_nomenclature_view ORDER BY label");
|
|
||||||
if (!query.exec()) {
|
if (!query.exec()) {
|
||||||
err << "BOM query failed: " << query.lastError().text() << "\n";
|
err << "BOM query failed: " << query.lastError().text() << "\n";
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString csv = columns.join(";") % "\n";
|
|
||||||
int rows = 0;
|
int rows = 0;
|
||||||
while (query.next()) {
|
const auto csv = BomExport::toCsv(
|
||||||
QStringList values;
|
query, BomExport::defaultColumns(), true, &rows);
|
||||||
for (int i = 0; i < columns.size(); ++i)
|
QString error;
|
||||||
values << csvField(query.value(i).toString());
|
if (!BomExport::writeCsv(output, csv, &error)) {
|
||||||
csv += values.join(";") % "\n";
|
err << "Cannot write '" << output << "': " << error << "\n";
|
||||||
++rows;
|
|
||||||
}
|
|
||||||
|
|
||||||
QFile file(output);
|
|
||||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
|
||||||
err << "Cannot open '" << output << "' for writing.\n";
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
QTextStream fout(&file);
|
|
||||||
fout << csv;
|
|
||||||
file.close();
|
|
||||||
out << "Exported " << rows << " component(s) -> " << output << "\n";
|
out << "Exported " << rows << " component(s) -> " << output << "\n";
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString csvField(const QString &value)
|
||||||
|
{
|
||||||
|
if (value.contains(QLatin1Char(';')) || value.contains(QLatin1Char('"'))
|
||||||
|
|| value.contains(QLatin1Char('\n')) || value.contains(QLatin1Char('\r')))
|
||||||
|
{
|
||||||
|
QString escaped = value;
|
||||||
|
escaped.replace(QLatin1Char('"'), QStringLiteral("\"\""));
|
||||||
|
return QLatin1Char('"') % escaped % QLatin1Char('"');
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
/// Count terminals on @p element that no conductor connects to.
|
/// Count terminals on @p element that no conductor connects to.
|
||||||
int freeTerminals(Element *element)
|
int freeTerminals(Element *element)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -600,6 +600,11 @@ void projectDataBase::createElementNomenclatureView()
|
|||||||
"ei.designation AS designation,"
|
"ei.designation AS designation,"
|
||||||
"ei.manufacturer AS manufacturer,"
|
"ei.manufacturer AS manufacturer,"
|
||||||
"ei.manufacturer_reference AS manufacturer_reference,"
|
"ei.manufacturer_reference AS manufacturer_reference,"
|
||||||
|
"ei.model AS model,"
|
||||||
|
"ei.category AS category,"
|
||||||
|
"ei.voltage_rating AS voltage_rating,"
|
||||||
|
"ei.current_rating AS current_rating,"
|
||||||
|
"ei.notes AS notes,"
|
||||||
"ei.machine_manufacturer_reference AS machine_manufacturer_reference,"
|
"ei.machine_manufacturer_reference AS machine_manufacturer_reference,"
|
||||||
"ei.supplier AS supplier,"
|
"ei.supplier AS supplier,"
|
||||||
"ei.quantity AS quantity,"
|
"ei.quantity AS quantity,"
|
||||||
@@ -658,7 +663,8 @@ void projectDataBase::createElementNomenclatureView()
|
|||||||
"di.folio AS folio,"
|
"di.folio AS folio,"
|
||||||
"e.pos AS position "
|
"e.pos AS position "
|
||||||
" FROM element_info ei, diagram_info di, element e, diagram d"
|
" FROM element_info ei, diagram_info di, element e, diagram d"
|
||||||
" WHERE ei.element_uuid = e.uuid AND e.diagram_uuid = d.uuid AND di.diagram_uuid = d.uuid AND (ei.exclude_from_bom IS NOT 'true')"
|
" WHERE ei.element_uuid = e.uuid AND e.diagram_uuid = d.uuid AND di.diagram_uuid = d.uuid"
|
||||||
|
" AND COALESCE(LOWER(TRIM(ei.exclude_from_bom)), '') NOT IN ('true', '1', 'yes', 'on')"
|
||||||
//The element table holds every element of the project; which
|
//The element table holds every element of the project; which
|
||||||
//kinds belong in a nomenclature is this view's business, not
|
//kinds belong in a nomenclature is this view's business, not
|
||||||
//the table's. Kept identical to the mask populateElementTable()
|
//the table's. Kept identical to the mask populateElementTable()
|
||||||
|
|||||||
@@ -160,6 +160,8 @@ void ElementQueryWidget::setQuery(const QString &query)
|
|||||||
{
|
{
|
||||||
auto str_type = rxm.captured(1);
|
auto str_type = rxm.captured(1);
|
||||||
where.remove(str_type);
|
where.remove(str_type);
|
||||||
|
const bool all_master_types = str_type.contains(
|
||||||
|
ElementData::typeToString(ElementData::Master));
|
||||||
|
|
||||||
int c=0;
|
int c=0;
|
||||||
ui->m_simple_cb->setChecked (str_type.contains(ElementData::typeToString(ElementData::Simple)) ? true : false);
|
ui->m_simple_cb->setChecked (str_type.contains(ElementData::typeToString(ElementData::Simple)) ? true : false);
|
||||||
@@ -170,23 +172,23 @@ void ElementQueryWidget::setQuery(const QString &query)
|
|||||||
if (ui->m_terminal_cb->isChecked()) {
|
if (ui->m_terminal_cb->isChecked()) {
|
||||||
++c;
|
++c;
|
||||||
}
|
}
|
||||||
ui->m_coil_cb->setChecked (str_type.contains(ElementData::masterTypeToString(ElementData::Coil)) ? true : false);
|
ui->m_coil_cb->setChecked (all_master_types || str_type.contains(ElementData::masterTypeToString(ElementData::Coil)));
|
||||||
if (ui->m_coil_cb->isChecked()) {
|
if (ui->m_coil_cb->isChecked()) {
|
||||||
++c;
|
++c;
|
||||||
}
|
}
|
||||||
ui->m_button_cb->setChecked (str_type.contains(ElementData::masterTypeToString(ElementData::Commutator)) ? true : false);
|
ui->m_button_cb->setChecked (all_master_types || str_type.contains(ElementData::masterTypeToString(ElementData::Commutator)));
|
||||||
if (ui->m_button_cb->isChecked()) {
|
if (ui->m_button_cb->isChecked()) {
|
||||||
++c;
|
++c;
|
||||||
}
|
}
|
||||||
ui->m_protection_cb->setChecked(str_type.contains(ElementData::masterTypeToString(ElementData::Protection)) ? true : false);
|
ui->m_protection_cb->setChecked(all_master_types || str_type.contains(ElementData::masterTypeToString(ElementData::Protection)));
|
||||||
if (ui->m_protection_cb) {
|
if (ui->m_protection_cb->isChecked()) {
|
||||||
++c;
|
++c;
|
||||||
}
|
}
|
||||||
ui->m_thumbnail_cb->setChecked (str_type.contains(ElementData::typeToString(ElementData::Thumbnail)) ? true : false);
|
ui->m_thumbnail_cb->setChecked (str_type.contains(ElementData::typeToString(ElementData::Thumbnail)) ? true : false);
|
||||||
if (ui->m_thumbnail_cb->isChecked()) {
|
if (ui->m_thumbnail_cb->isChecked()) {
|
||||||
++c;
|
++c;
|
||||||
}
|
}
|
||||||
ui->m_plc_cb->setChecked (str_type.contains(ElementData::masterTypeToString(ElementData::PLC)) ? true : false);
|
ui->m_plc_cb->setChecked (all_master_types || str_type.contains(ElementData::masterTypeToString(ElementData::PLC)));
|
||||||
if (ui->m_plc_cb->isChecked()) {
|
if (ui->m_plc_cb->isChecked()) {
|
||||||
++c;
|
++c;
|
||||||
}
|
}
|
||||||
@@ -352,22 +354,32 @@ QString ElementQueryWidget::queryStr() const
|
|||||||
where += QStringLiteral(" element_type = '") += ElementData::typeToString(ElementData::Simple) += "'";
|
where += QStringLiteral(" element_type = '") += ElementData::typeToString(ElementData::Simple) += "'";
|
||||||
b = true;
|
b = true;
|
||||||
}
|
}
|
||||||
if (ui->m_button_cb->isChecked()) {
|
const bool all_master_types = ui->m_button_cb->isChecked()
|
||||||
|
&& ui->m_coil_cb->isChecked()
|
||||||
|
&& ui->m_protection_cb->isChecked()
|
||||||
|
&& ui->m_plc_cb->isChecked();
|
||||||
|
if (all_master_types) {
|
||||||
|
if (b) where += " OR";
|
||||||
|
where += QStringLiteral(" element_type = '")
|
||||||
|
+= ElementData::typeToString(ElementData::Master) += "'";
|
||||||
|
b = true;
|
||||||
|
}
|
||||||
|
if (!all_master_types && ui->m_button_cb->isChecked()) {
|
||||||
if (b) where +=" OR";
|
if (b) where +=" OR";
|
||||||
where += QStringLiteral(" element_sub_type = '") += ElementData::masterTypeToString(ElementData::Commutator) += "'";
|
where += QStringLiteral(" element_sub_type = '") += ElementData::masterTypeToString(ElementData::Commutator) += "'";
|
||||||
b = true;
|
b = true;
|
||||||
}
|
}
|
||||||
if (ui->m_coil_cb->isChecked()) {
|
if (!all_master_types && ui->m_coil_cb->isChecked()) {
|
||||||
if (b) where +=" OR";
|
if (b) where +=" OR";
|
||||||
where += QStringLiteral(" element_sub_type = '") += ElementData::masterTypeToString(ElementData::Coil) += "'";
|
where += QStringLiteral(" element_sub_type = '") += ElementData::masterTypeToString(ElementData::Coil) += "'";
|
||||||
b = true;
|
b = true;
|
||||||
}
|
}
|
||||||
if (ui->m_protection_cb->isChecked()) {
|
if (!all_master_types && ui->m_protection_cb->isChecked()) {
|
||||||
if (b) where +=" OR";
|
if (b) where +=" OR";
|
||||||
where += QStringLiteral(" element_sub_type = '") += ElementData::masterTypeToString(ElementData::Protection) += "'";
|
where += QStringLiteral(" element_sub_type = '") += ElementData::masterTypeToString(ElementData::Protection) += "'";
|
||||||
b = true;
|
b = true;
|
||||||
}
|
}
|
||||||
if (ui->m_plc_cb->isChecked()) {
|
if (!all_master_types && ui->m_plc_cb->isChecked()) {
|
||||||
if (b) where +=" OR";
|
if (b) where +=" OR";
|
||||||
where += QStringLiteral(" element_sub_type = '") += ElementData::masterTypeToString(ElementData::PLC) += "'";
|
where += QStringLiteral(" element_sub_type = '") += ElementData::masterTypeToString(ElementData::PLC) += "'";
|
||||||
b = true;
|
b = true;
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2006-2026 The QElectroTech Team
|
||||||
|
This file is part of QElectroTech.
|
||||||
|
|
||||||
|
QElectroTech is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
QElectroTech is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#include "deviceinformation.h"
|
||||||
|
|
||||||
|
#include "../qetinformation.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
struct DeviceField
|
||||||
|
{
|
||||||
|
QString m_key;
|
||||||
|
QString DeviceInformation::*m_member;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DeviceField fields[] = {
|
||||||
|
{QETInformation::ELMT_LABEL, &DeviceInformation::m_device_tag},
|
||||||
|
{QETInformation::ELMT_MANUFACTURER, &DeviceInformation::m_manufacturer},
|
||||||
|
{QETInformation::ELMT_MANUFACTURER_REF, &DeviceInformation::m_part_number},
|
||||||
|
{QETInformation::ELMT_MODEL, &DeviceInformation::m_model},
|
||||||
|
{QETInformation::ELMT_DESCRIPTION, &DeviceInformation::m_description},
|
||||||
|
{QETInformation::ELMT_CATEGORY, &DeviceInformation::m_category},
|
||||||
|
{QETInformation::ELMT_QUANTITY, &DeviceInformation::m_quantity},
|
||||||
|
{QETInformation::ELMT_VOLTAGE_RATING, &DeviceInformation::m_voltage_rating},
|
||||||
|
{QETInformation::ELMT_CURRENT_RATING, &DeviceInformation::m_current_rating},
|
||||||
|
{QETInformation::ELMT_NOTES, &DeviceInformation::m_notes}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
DeviceInformation DeviceInformation::fromContext(const DiagramContext &context)
|
||||||
|
{
|
||||||
|
DeviceInformation info;
|
||||||
|
for (const auto &field : fields) {
|
||||||
|
info.*(field.m_member) = context.value(field.m_key).toString();
|
||||||
|
}
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
DiagramContext DeviceInformation::toContext(DiagramContext context) const
|
||||||
|
{
|
||||||
|
for (const auto &field : fields)
|
||||||
|
{
|
||||||
|
const auto &value = this->*(field.m_member);
|
||||||
|
if (value.isEmpty()) {
|
||||||
|
context.remove(field.m_key);
|
||||||
|
} else {
|
||||||
|
const bool show = !context.contains(field.m_key) || context.keyMustShow(field.m_key);
|
||||||
|
context.addValue(field.m_key, value, show);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2006-2026 The QElectroTech Team
|
||||||
|
This file is part of QElectroTech.
|
||||||
|
|
||||||
|
QElectroTech is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
QElectroTech is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#ifndef DEVICEINFORMATION_H
|
||||||
|
#define DEVICEINFORMATION_H
|
||||||
|
|
||||||
|
#include "../diagramcontext.h"
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Device properties backed by ElementData::m_informations, not a second store.
|
||||||
|
* Missing properties remain empty. Quantity and ratings retain the existing
|
||||||
|
* free-text representation (including decimal quantities and units).
|
||||||
|
*/
|
||||||
|
struct DeviceInformation
|
||||||
|
{
|
||||||
|
QString m_device_tag;
|
||||||
|
QString m_manufacturer;
|
||||||
|
QString m_part_number;
|
||||||
|
QString m_model;
|
||||||
|
QString m_description;
|
||||||
|
QString m_category;
|
||||||
|
QString m_quantity;
|
||||||
|
QString m_voltage_rating;
|
||||||
|
QString m_current_rating;
|
||||||
|
QString m_notes;
|
||||||
|
|
||||||
|
static DeviceInformation fromContext(const DiagramContext &context);
|
||||||
|
DiagramContext toContext(DiagramContext context = DiagramContext()) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // DEVICEINFORMATION_H
|
||||||
@@ -150,6 +150,11 @@ QStringList QETInformation::elementInfoKeys()
|
|||||||
ELMT_DESIGNATION,
|
ELMT_DESIGNATION,
|
||||||
ELMT_MANUFACTURER,
|
ELMT_MANUFACTURER,
|
||||||
ELMT_MANUFACTURER_REF,
|
ELMT_MANUFACTURER_REF,
|
||||||
|
ELMT_MODEL,
|
||||||
|
ELMT_CATEGORY,
|
||||||
|
ELMT_VOLTAGE_RATING,
|
||||||
|
ELMT_CURRENT_RATING,
|
||||||
|
ELMT_NOTES,
|
||||||
ELMT_MACHINE_MANUFACTURER_REF,
|
ELMT_MACHINE_MANUFACTURER_REF,
|
||||||
ELMT_SUPPLIER,
|
ELMT_SUPPLIER,
|
||||||
ELMT_QUANTITY,
|
ELMT_QUANTITY,
|
||||||
@@ -234,7 +239,12 @@ QString QETInformation::infoToVar(const QString &info)
|
|||||||
*/
|
*/
|
||||||
QString QETInformation::translatedInfoKey(const QString &info)
|
QString QETInformation::translatedInfoKey(const QString &info)
|
||||||
{
|
{
|
||||||
if (info == DIA_AUTHOR) return QObject::tr("Auteur");
|
if (info == ELMT_MODEL) return QObject::tr("Modèle");
|
||||||
|
else if (info == ELMT_CATEGORY) return QObject::tr("Catégorie");
|
||||||
|
else if (info == ELMT_VOLTAGE_RATING) return QObject::tr("Tension nominale");
|
||||||
|
else if (info == ELMT_CURRENT_RATING) return QObject::tr("Courant nominal");
|
||||||
|
else if (info == ELMT_NOTES) return QObject::tr("Notes");
|
||||||
|
else if (info == DIA_AUTHOR) return QObject::tr("Auteur");
|
||||||
else if (info == DIA_DATE) return QObject::tr("Date");
|
else if (info == DIA_DATE) return QObject::tr("Date");
|
||||||
else if (info == DIA_TITLE) return QObject::tr("Titre");
|
else if (info == DIA_TITLE) return QObject::tr("Titre");
|
||||||
else if (info == DIA_FILENAME) return QObject::tr("Fichier");
|
else if (info == DIA_FILENAME) return QObject::tr("Fichier");
|
||||||
@@ -332,6 +342,11 @@ QStringList QETInformation::elementEditorElementInfoKeys()
|
|||||||
ELMT_DESIGNATION,
|
ELMT_DESIGNATION,
|
||||||
ELMT_MANUFACTURER,
|
ELMT_MANUFACTURER,
|
||||||
ELMT_MANUFACTURER_REF,
|
ELMT_MANUFACTURER_REF,
|
||||||
|
ELMT_MODEL,
|
||||||
|
ELMT_CATEGORY,
|
||||||
|
ELMT_VOLTAGE_RATING,
|
||||||
|
ELMT_CURRENT_RATING,
|
||||||
|
ELMT_NOTES,
|
||||||
ELMT_MACHINE_MANUFACTURER_REF,
|
ELMT_MACHINE_MANUFACTURER_REF,
|
||||||
ELMT_SUPPLIER,
|
ELMT_SUPPLIER,
|
||||||
ELMT_QUANTITY,
|
ELMT_QUANTITY,
|
||||||
|
|||||||
@@ -44,6 +44,11 @@ namespace QETInformation
|
|||||||
static QString ELMT_MACHINE_MANUFACTURER_REF = "machine_manufacturer_reference";
|
static QString ELMT_MACHINE_MANUFACTURER_REF = "machine_manufacturer_reference";
|
||||||
static QString ELMT_SUPPLIER = "supplier";
|
static QString ELMT_SUPPLIER = "supplier";
|
||||||
static QString ELMT_QUANTITY = "quantity";
|
static QString ELMT_QUANTITY = "quantity";
|
||||||
|
static QString ELMT_MODEL = "model";
|
||||||
|
static QString ELMT_CATEGORY = "category";
|
||||||
|
static QString ELMT_VOLTAGE_RATING = "voltage_rating";
|
||||||
|
static QString ELMT_CURRENT_RATING = "current_rating";
|
||||||
|
static QString ELMT_NOTES = "notes";
|
||||||
static QString ELMT_UNITY = "unity";
|
static QString ELMT_UNITY = "unity";
|
||||||
static QString ELMT_PLANT = "plant";
|
static QString ELMT_PLANT = "plant";
|
||||||
static QString ELMT_LOCATION = "location";
|
static QString ELMT_LOCATION = "location";
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
#include "bomexportdialog.h"
|
#include "bomexportdialog.h"
|
||||||
|
|
||||||
#include "../dataBase/ui/elementquerywidget.h"
|
#include "../dataBase/ui/elementquerywidget.h"
|
||||||
|
#include "../bomexport.h"
|
||||||
#include "../qetapp.h"
|
#include "../qetapp.h"
|
||||||
#include "../qetinformation.h"
|
#include "../qetinformation.h"
|
||||||
#include "../qetproject.h"
|
#include "../qetproject.h"
|
||||||
@@ -41,8 +42,8 @@ BOMExportDialog::BOMExportDialog(QETProject *project, QWidget *parent) :
|
|||||||
|
|
||||||
m_query_widget = new ElementQueryWidget(this);
|
m_query_widget = new ElementQueryWidget(this);
|
||||||
ui->m_main_layout->insertWidget(0, m_query_widget);
|
ui->m_main_layout->insertWidget(0, m_query_widget);
|
||||||
//By default format as bom is clicked
|
m_query_widget->setQuery(BomExport::defaultQuery());
|
||||||
on_m_format_as_bom_clicked(true);
|
on_m_format_as_bom_clicked(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -67,90 +68,58 @@ int BOMExportDialog::exec()
|
|||||||
if (dir.isEmpty()) dir = QETApp::documentDir();
|
if (dir.isEmpty()) dir = QETApp::documentDir();
|
||||||
QString file_name = dir % "/" % tr("nomenclature_") % QString(m_project ->title() % ".csv");
|
QString file_name = dir % "/" % tr("nomenclature_") % QString(m_project ->title() % ".csv");
|
||||||
QString file_path = QFileDialog::getSaveFileName(this, tr("Enregister sous... "), file_name, tr("Fichiers csv (*.csv)"));
|
QString file_path = QFileDialog::getSaveFileName(this, tr("Enregister sous... "), file_name, tr("Fichiers csv (*.csv)"));
|
||||||
QFile file(file_path);
|
|
||||||
if (!file_path.isEmpty())
|
if (!file_path.isEmpty())
|
||||||
{
|
{
|
||||||
if (QFile::exists(file_path ))
|
QString error;
|
||||||
{
|
const auto csv = getBom(&error);
|
||||||
// if file already exist -> delete it
|
if (!error.isEmpty() || !BomExport::writeCsv(file_path, csv, &error)) {
|
||||||
if (!QFile::remove(file_path) )
|
QMessageBox::critical(
|
||||||
{
|
this, tr("Erreur"),
|
||||||
QMessageBox::critical(this, tr("Erreur"),
|
tr("Impossible d'enregistrer la nomenclature dans %1.\n%2")
|
||||||
tr("Impossible de remplacer le fichier!\n\n")+
|
.arg(file_path, error));
|
||||||
"Destination : "+file_path+"\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (file.open(QIODevice::WriteOnly | QIODevice::Text))
|
|
||||||
{
|
|
||||||
QTextStream stream(&file);
|
|
||||||
stream << getBom() << &Qt::endl(stream);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString BOMExportDialog::getBom()
|
QByteArray BOMExportDialog::getBom(QString *error)
|
||||||
{
|
{
|
||||||
|
if (error) {
|
||||||
|
error->clear();
|
||||||
|
}
|
||||||
m_project->dataBase()->updateDB();
|
m_project->dataBase()->updateDB();
|
||||||
auto query_ = m_project->dataBase()->newQuery(m_query_widget->queryStr());
|
auto query_ = m_project->dataBase()->newQuery(m_query_widget->queryStr());
|
||||||
QString return_string;
|
|
||||||
|
|
||||||
if (!query_.exec()) {
|
if (!query_.exec()) {
|
||||||
qDebug() << "BOMExportDialog::getBom : query errir : " << query_.lastError();
|
qDebug() << "BOMExportDialog::getBom : query errir : " << query_.lastError();
|
||||||
|
if (error) {
|
||||||
|
*error = query_.lastError().text();
|
||||||
|
}
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
QStringList header_names;
|
||||||
|
if (ui->m_include_headers->isChecked())
|
||||||
{
|
{
|
||||||
//HEADERS
|
const auto record_ = query_.record();
|
||||||
if (ui->m_include_headers)
|
for (int i = 0; i < record_.count(); ++i)
|
||||||
{
|
{
|
||||||
auto record_ = query_.record();
|
const auto field_name = record_.fieldName(i);
|
||||||
QStringList header_name;
|
if (field_name == QLatin1String("position")) {
|
||||||
for (auto i=0 ; i<record_.count() ; ++i)
|
header_names << tr("Position");
|
||||||
{
|
} else if (field_name == QLatin1String("diagram_position")) {
|
||||||
auto field_name = record_.fieldName(i);
|
header_names << tr("Position du folio");
|
||||||
|
} else if (field_name == QLatin1String("designation_qty")) {
|
||||||
qDebug() << "field name = " << field_name;
|
header_names << tr("Quantité numéro d'article", "Special field with name : designation quantity");
|
||||||
if (field_name == "position") {
|
} else {
|
||||||
header_name << tr("Position");
|
const auto translated = QETInformation::translatedInfoKey(field_name);
|
||||||
} else if (field_name == "diagram_position") {
|
header_names << (translated.isEmpty() ? field_name : translated);
|
||||||
header_name << tr("Position du folio");
|
|
||||||
} else if (field_name == "designation_qty") {
|
|
||||||
header_name << tr("Quantité numéro d'article", "Special field with name : designation quantity");
|
|
||||||
} else {
|
|
||||||
header_name << QETInformation::translatedInfoKey(field_name);
|
|
||||||
if (header_name.isEmpty()) {
|
|
||||||
header_name << field_name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
return_string = header_name.join(";") % "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
//ROWS
|
|
||||||
while (query_.next())
|
|
||||||
{
|
|
||||||
auto i=0;
|
|
||||||
QStringList values;
|
|
||||||
while (query_.value(i).isValid())
|
|
||||||
{
|
|
||||||
auto date = query_.value(i).toDate();
|
|
||||||
if (!date.isNull()) {
|
|
||||||
values << QLocale::system().toString(query_.value(i).toDate(), QLocale::ShortFormat);
|
|
||||||
} else {
|
|
||||||
values << query_.value(i).toString();
|
|
||||||
}
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
|
|
||||||
return_string += values.join(";") % "\n";
|
|
||||||
values.clear();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return BomExport::toCsv(
|
||||||
qDebug() << return_string;
|
query_, header_names, ui->m_include_headers->isChecked());
|
||||||
return return_string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
#define BOMEXPORTDIALOG_H
|
#define BOMEXPORTDIALOG_H
|
||||||
|
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
|
#include <QByteArray>
|
||||||
|
|
||||||
class QETProject;
|
class QETProject;
|
||||||
class ElementQueryWidget;
|
class ElementQueryWidget;
|
||||||
@@ -39,7 +40,7 @@ class BOMExportDialog : public QDialog
|
|||||||
~BOMExportDialog() override;
|
~BOMExportDialog() override;
|
||||||
|
|
||||||
virtual int exec() override;
|
virtual int exec() override;
|
||||||
QString getBom();
|
QByteArray getBom(QString *error = nullptr);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void on_m_format_as_bom_clicked(bool checked);
|
void on_m_format_as_bom_clicked(bool checked);
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
<string>Formater en tant que liste de materiel</string>
|
<string>Formater en tant que liste de materiel</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="checked">
|
<property name="checked">
|
||||||
<bool>true</bool>
|
<bool>false</bool>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
|||||||
@@ -91,3 +91,12 @@ add_executable(tst_contactusage tst_contactusage.cpp)
|
|||||||
add_test(NAME tst_contactusage COMMAND tst_contactusage)
|
add_test(NAME tst_contactusage COMMAND tst_contactusage)
|
||||||
target_include_directories(tst_contactusage PRIVATE ${QET_DIR}/sources)
|
target_include_directories(tst_contactusage PRIVATE ${QET_DIR}/sources)
|
||||||
target_link_libraries(tst_contactusage PRIVATE Qt::Test)
|
target_link_libraries(tst_contactusage PRIVATE Qt::Test)
|
||||||
|
|
||||||
|
add_executable(
|
||||||
|
tst_smart_device
|
||||||
|
tst_smart_device.cpp
|
||||||
|
${QET_DIR}/sources/bomexport.cpp
|
||||||
|
${QET_DIR}/sources/bomexport.h)
|
||||||
|
add_test(NAME tst_smart_device COMMAND tst_smart_device)
|
||||||
|
target_include_directories(tst_smart_device PRIVATE ${QET_DIR}/sources)
|
||||||
|
target_link_libraries(tst_smart_device PRIVATE Qt::Test Qt::Sql)
|
||||||
|
|||||||
@@ -0,0 +1,135 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2006-2026 The QElectroTech Team
|
||||||
|
This file is part of QElectroTech.
|
||||||
|
|
||||||
|
QElectroTech is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
QElectroTech is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#include "bomexport.h"
|
||||||
|
|
||||||
|
#include <QFile>
|
||||||
|
#include <QSqlDatabase>
|
||||||
|
#include <QSqlError>
|
||||||
|
#include <QSqlQuery>
|
||||||
|
#include <QTemporaryDir>
|
||||||
|
#include <QtTest>
|
||||||
|
|
||||||
|
class SmartDeviceTest : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void metadataColumns();
|
||||||
|
void defaultQueryAndCsv();
|
||||||
|
void atomicWrite();
|
||||||
|
};
|
||||||
|
|
||||||
|
void SmartDeviceTest::metadataColumns()
|
||||||
|
{
|
||||||
|
const auto columns = BomExport::defaultColumns();
|
||||||
|
for (const QString &column : {
|
||||||
|
QStringLiteral("label"), QStringLiteral("manufacturer"),
|
||||||
|
QStringLiteral("manufacturer_reference"), QStringLiteral("model"),
|
||||||
|
QStringLiteral("description"), QStringLiteral("category"),
|
||||||
|
QStringLiteral("quantity"), QStringLiteral("voltage_rating"),
|
||||||
|
QStringLiteral("current_rating"), QStringLiteral("folio"),
|
||||||
|
QStringLiteral("notes")})
|
||||||
|
{
|
||||||
|
QVERIFY2(columns.contains(column), qPrintable(column));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SmartDeviceTest::defaultQueryAndCsv()
|
||||||
|
{
|
||||||
|
const QString connection = QStringLiteral("smart-device-%1")
|
||||||
|
.arg(QUuid::createUuid().toString());
|
||||||
|
{
|
||||||
|
auto db = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), connection);
|
||||||
|
db.setDatabaseName(QStringLiteral(":memory:"));
|
||||||
|
QVERIFY2(db.open(), qPrintable(db.lastError().text()));
|
||||||
|
QSqlQuery setup(db);
|
||||||
|
QVERIFY(setup.exec(QStringLiteral(
|
||||||
|
"CREATE TABLE element_nomenclature_view ("
|
||||||
|
"label TEXT, designation TEXT, manufacturer TEXT, "
|
||||||
|
"manufacturer_reference TEXT, model TEXT, description TEXT, "
|
||||||
|
"category TEXT, quantity TEXT, voltage_rating TEXT, "
|
||||||
|
"current_rating TEXT, folio TEXT, notes TEXT, "
|
||||||
|
"element_type TEXT, diagram_position INTEGER, position INTEGER)")));
|
||||||
|
|
||||||
|
QSqlQuery insert(db);
|
||||||
|
insert.prepare(QStringLiteral(
|
||||||
|
"INSERT INTO element_nomenclature_view VALUES "
|
||||||
|
"(?, '', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"));
|
||||||
|
auto add = [&insert](const QString &tag, const QString &type, int page,
|
||||||
|
int position, const QString &manufacturer) {
|
||||||
|
insert.bindValue(0, tag);
|
||||||
|
insert.bindValue(1, manufacturer);
|
||||||
|
insert.bindValue(2, QStringLiteral("0012"));
|
||||||
|
insert.bindValue(3, QStringLiteral("M-1"));
|
||||||
|
insert.bindValue(4, QStringLiteral("Power contactor"));
|
||||||
|
insert.bindValue(5, QStringLiteral("Control"));
|
||||||
|
insert.bindValue(6, QStringLiteral("1"));
|
||||||
|
insert.bindValue(7, QStringLiteral("24 V DC"));
|
||||||
|
insert.bindValue(8, QStringLiteral("16 A"));
|
||||||
|
insert.bindValue(9, QString::number(page));
|
||||||
|
insert.bindValue(10, QStringLiteral("Quoted \"note\"\nnext line"));
|
||||||
|
insert.bindValue(11, type);
|
||||||
|
insert.bindValue(12, page);
|
||||||
|
insert.bindValue(13, position);
|
||||||
|
return insert.exec();
|
||||||
|
};
|
||||||
|
QVERIFY(add(QStringLiteral("-K2"), QStringLiteral("simple"), 2, 1,
|
||||||
|
QStringLiteral("日本電機")));
|
||||||
|
QVERIFY(add(QStringLiteral("-K1"), QStringLiteral("master"), 1, 2,
|
||||||
|
QStringLiteral("Müller")));
|
||||||
|
QVERIFY(add(QStringLiteral("-K1.1"), QStringLiteral("slave"), 1, 3,
|
||||||
|
QStringLiteral("Must not be exported")));
|
||||||
|
QVERIFY(add(QStringLiteral("X1"), QStringLiteral("terminal"), 1, 4,
|
||||||
|
QStringLiteral("Must not be exported")));
|
||||||
|
|
||||||
|
QSqlQuery query(db);
|
||||||
|
QVERIFY2(query.exec(BomExport::defaultQuery()),
|
||||||
|
qPrintable(query.lastError().text()));
|
||||||
|
int rows = 0;
|
||||||
|
const auto csv = BomExport::toCsv(
|
||||||
|
query, BomExport::defaultColumns(), true, &rows);
|
||||||
|
QCOMPARE(rows, 2);
|
||||||
|
QVERIFY(csv.startsWith("\xEF\xBB\xBF\"label\";\"designation\";"));
|
||||||
|
QVERIFY(csv.contains(QStringLiteral("Müller").toUtf8()));
|
||||||
|
QVERIFY(csv.contains(QStringLiteral("日本電機").toUtf8()));
|
||||||
|
QVERIFY(csv.contains("\"Quoted \"\"note\"\"\nnext line\""));
|
||||||
|
QVERIFY(!csv.contains("Must not be exported"));
|
||||||
|
QVERIFY(csv.indexOf("-K1") < csv.indexOf("-K2"));
|
||||||
|
}
|
||||||
|
QSqlDatabase::removeDatabase(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SmartDeviceTest::atomicWrite()
|
||||||
|
{
|
||||||
|
QTemporaryDir temporary;
|
||||||
|
QVERIFY(temporary.isValid());
|
||||||
|
const auto path = temporary.filePath(QStringLiteral("bom.csv"));
|
||||||
|
const QByteArray csv("\xEF\xBB\xBF\"label\"\n");
|
||||||
|
QString error = QStringLiteral("stale");
|
||||||
|
QVERIFY(BomExport::writeCsv(path, csv, &error));
|
||||||
|
QVERIFY(error.isEmpty());
|
||||||
|
QFile file(path);
|
||||||
|
QVERIFY(file.open(QIODevice::ReadOnly));
|
||||||
|
QCOMPARE(file.readAll(), csv);
|
||||||
|
QVERIFY(!BomExport::writeCsv(
|
||||||
|
temporary.filePath(QStringLiteral("missing/bom.csv")), csv, &error));
|
||||||
|
QVERIFY(!error.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
QTEST_GUILESS_MAIN(SmartDeviceTest)
|
||||||
|
#include "tst_smart_device.moc"
|
||||||
Reference in New Issue
Block a user