Skip to content

Commit

Permalink
Add Excel support & make it version 1.0.0 (#20)
Browse files Browse the repository at this point in the history
* Add excel dependency

* Upgrade some transitive dependencies

* Start implementing file Excel format selection

* Add a default choice for export path & document it

* Use table in readme file

* Fix empty cells bug in readme

* Update Dart SDK version number to the latest stable

* Prepare for exporting receiptProducts as xlsx

* Add home_directory_helper & use it. Use path package (cross-platform)

* Fine-tune printSelectedValues (fix indentations)

* Add Excel exports for eanProducts & receiptProducts

* Clean pubspec.yaml

* Add cellStyle for receiptProducts xlsx

* Add a minimum example & change version number (use 1.0.0)

* Bold some texts in readme

* Small update into replaceTildeWithHomeDirectory method

* Some small K-ruoka specific fixes into runMainProgram

* Add a todo message for K-ruoka EAN products

* Add Excel stylizing for EAN products (& rename ean)

* Refactoring (add SheetExtension for updating the row style)

* Add green background color for some extra products

* Update Dart SDK version & some transitive dependencies

* Remove assets section from pubspec

* A small update into the installation file

* Update readme
  • Loading branch information
areee authored Sep 12, 2022
1 parent 8d11630 commit 4e1b9fd
Show file tree
Hide file tree
Showing 21 changed files with 351 additions and 68 deletions.
2 changes: 1 addition & 1 deletion INSTALLATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@ alias kassakuitti='dart run $HOME/Documents/dart_kassakuitti_cli/bin/dart_kassak
If you're unsure where to save the alias, this might help you:

- If you're using _Zsh_ as your shell, use `~/.zshrc` profile file.
- If you're using _Bash_ as your shell, use `~/.bash_profile` profile file.
- If you're using _Bash_ as your shell, you could use `~/.bashrc` or `~/.bash_profile` profile file (or e.g. `~/.bash_aliases` file).
28 changes: 18 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

# dart_kassakuitti_cli

> A simple Dart CLI app to handle a cash receipt coming from S-kaupat or K-ruoka (two Finnish food online stores).
> A simple Dart CLI app to handle a cash receipt coming from S-kaupat or K-ruoka (two Finnish food online stores). Fully compatible with macOS, Linux & Windows.
## Installation

Expand All @@ -30,21 +30,29 @@ See the installation in [its own page](https://github.com/areee/dart_kassakuitti
The basic usage looks like this:

```
kassakuitti run -t [a path to the cash receipt file] -h [a path to the EAN file] -s [S-kaupat or K-ruoka] -c [a path to generated CSV files]
kassakuitti run -t [a path to the cash receipt file] -h [a path to the EAN file] -s [S-kaupat or K-ruoka] -p [a path to generated files] -f [csv or Excel = xlsx]
```

You can define
- a cash receipt (`-t` = text file),
- an EAN products file (`-h` = html file),
- optionally: which food online store to use (`-s` = store) and
- where to save the output files (`-c` = CSV file).
#### Choices (flags) for `kassakuitti run` command

S-kaupat is a default choice for the food online store selection (`-s`).
Mandatory | Abbreviation | Meaning | Default choice
:---: | :---: | :---: | :---:
✅ | `-t` | **Text** file (a cash receipt) | -
✅ | `-h` | **Html** file (an EAN products file) | -
  | `-s` | Which food online **store** to use | S-kaupat
  | `-p` | **Path** where to save the output files | User's Downloads folder (`~/Downloads`)
  | `-f` | In which file **format** the output files will be saved | csv

#### An example
#### An example #1 (minimum choices)

```
kassakuitti run -t /Users/username/Downloads/cash_receipt.txt -h /Users/username/Downloads/https___www.s-kaupat.fi_tilaus_product_id-generating_time.html -s S-kaupat -c ~/Downloads
kassakuitti run -t /Users/username/Downloads/cash_receipt.txt -h /Users/username/Downloads/https___www.s-kaupat.fi_tilaus_product_id-generating_time.html
```

#### An example #2 (maximum choices)

```
kassakuitti run -t /Users/username/Downloads/cash_receipt.txt -h /Users/username/Downloads/https___www.s-kaupat.fi_tilaus_product_id-generating_time.html -s S-kaupat -p ~/Downloads -f csv
```

### Help
Expand Down
14 changes: 9 additions & 5 deletions bin/ean_products_2_csv.dart
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
import 'dart:io';

import 'package:path/path.dart' as p;

import 'models/ean_product.dart';
import 'utils/date_helper.dart';
import 'utils/home_directory_helper.dart';

void eanProducts2CSV(
List<EANProduct> eanProductList, String csvFilePath, String shopSelector) {
void eanProducts2CSV(List<EANProduct> eanProductList, String exportFilePath,
String shopSelector) {
var csv = StringBuffer();

csv.write('Name;Quantity;Price per unit;Total price;EAN code;More details\n');

for (var item in eanProductList) {
csv.write(
'${item.name};${item.quantity};${item.pricePerUnit};${item.totalPrice};${item.ean};${item.moreDetails}\n');
'${item.name};${item.quantity};${item.pricePerUnit};${item.totalPrice};${item.eanCode};${item.moreDetails}\n');
}

var file = File(
'$csvFilePath/${shopSelector}_ean_products_${formattedDateTime()}.csv');
var file = File(p.join(replaceTildeWithHomeDirectory(exportFilePath),
'${shopSelector}_ean_products_${formattedDateTime()}.csv'));
file.writeAsString(csv.toString());
}
68 changes: 68 additions & 0 deletions bin/ean_products_2_xlsx.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import 'dart:io';

import 'package:excel/excel.dart';
import 'package:path/path.dart' as p;

import 'models/ean_product.dart';
import 'utils/date_helper.dart';
import 'utils/home_directory_helper.dart';
import 'utils/extensions/sheet_extension.dart';

/// Saves EAN products as an Excel (xlsx) file.
void eanProducts2Excel(
List<EANProduct> products, String exportFilePath, String shopSelector) {
var excel = Excel.createExcel();
var sheetObject = excel.sheets[excel.getDefaultSheet()];

// Write the header.
var headerDataList = [
"Name",
"Quantity",
"Price per unit",
"Total price",
"EAN code",
"More details"
];
sheetObject?.insertRowIterables(headerDataList, 0);
sheetObject?.updateSelectedRowStyle(0, CellStyle(bold: true));

// Write the products.
for (var product in products) {
var productDataList = [
product.name,
product.quantity,
product.pricePerUnit,
product.totalPrice,
product.eanCode,
product.moreDetails
];
sheetObject?.insertRowIterables(
productDataList, products.indexOf(product) + 1);

// If the product is a fruit or vegetable, change the background color to green.
if (product.eanCode.startsWith("2") || product.eanCode.startsWith("02")) {
sheetObject?.updateSelectedRowStyle(products.indexOf(product) + 1,
CellStyle(backgroundColorHex: "#1AFF1A"));
}
// If the product is a packaging material,
// change the font color to red and the background color to green.
if (product.name == 'Pakkausmateriaalikustannukset') {
sheetObject?.updateSelectedRowStyle(products.indexOf(product) + 1,
CellStyle(fontColorHex: "#FF0000", backgroundColorHex: "#1AFF1A"));
}

// If the product is a home delivery, change the background color to green.
if (product.name == 'Kotiinkuljetus') {
sheetObject?.updateSelectedRowStyle(products.indexOf(product) + 1,
CellStyle(backgroundColorHex: "#1AFF1A"));
}
}

// Save to the Excel (xlsx) file:
var fileBytes = excel.save();

File(p.join(replaceTildeWithHomeDirectory(exportFilePath),
'${shopSelector}_ean_products_${formattedDateTime()}.xlsx'))
..createSync(recursive: true)
..writeAsBytesSync(fileBytes!);
}
4 changes: 2 additions & 2 deletions bin/models/ean_product.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
class EANProduct {
final String ean;
final String eanCode;
final String name;
final int quantity;
final String totalPrice;
final String pricePerUnit;
final String moreDetails;

EANProduct(
{this.ean = '0000000000000',
{this.eanCode = '0000000000000',
this.name = 'Default EAN product name',
this.quantity = 1,
this.totalPrice = '0',
Expand Down
2 changes: 1 addition & 1 deletion bin/read_ean_products.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import 'specific/k_ruoka/load_html_k_ruoka.dart' as k_ruoka;
import 'utils/shop_selector_helper.dart';

Future<List<EANProduct>> readEANProducts(
String filePath, ShopSelector shopSelector, String csvFilePath) async {
String filePath, ShopSelector shopSelector) async {
switch (shopSelector) {
case ShopSelector.sKaupat:
return await s_kaupat.loadHtmlFromAssets(filePath);
Expand Down
62 changes: 49 additions & 13 deletions bin/run_main_program.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ import 'package:args/args.dart';
import 'package:hive/hive.dart';

import 'ean_products_2_csv.dart';
import 'ean_products_2_xlsx.dart';
import 'models/hive_product.dart';
import 'read_ean_products.dart';
import 'specific/s_kaupat/ean_handler.dart';
import 'specific/s_kaupat/read_receipt_products.dart';
import 'specific/s_kaupat/receipt_products_2_csv.dart';
import 'specific/s_kaupat/receipt_products_2_xlsx.dart';
import 'utils/arg_selector_helper.dart';
import 'utils/export_format_helper.dart';
import 'utils/printing_helper.dart';
import 'utils/shop_selector_helper.dart';

Expand All @@ -20,27 +23,60 @@ Future<Box<HiveProduct>> runMainProgram(
var selectedTextFile = argResults[ArgSelector.textFile.value] as String?;
var selectedHtmlFile = argResults[ArgSelector.htmlFile.value] as String;
var selectedStore = argResults[ArgSelector.foodOnlineStore.value] as String;
var csvFilesPath = argResults[ArgSelector.csvPath.value] as String;

printSelectedValues(
selectedTextFile, selectedHtmlFile, selectedStore, csvFilesPath);
var exportFilesPath = argResults[ArgSelector.exportPath.value] as String;
var exportFilesFormat = argResults[ArgSelector.exportFormat.value] as String;
/*
Check the HTML file name (it should contain k-ruoka or s-kaupat)
(By default, selectedStore is s-kaupat)
*/
selectedStore = selectedHtmlFile.contains('k-ruoka.fi')
? ShopSelector.kRuoka.value
: selectedStore;
printSelectedValues(selectedTextFile, selectedHtmlFile, selectedStore,
exportFilesPath, exportFilesFormat);

try {
if (ShopSelector.sKaupat.value == selectedStore) {
var receiptProducts =
await readReceiptProducts(selectedTextFile!, csvFilesPath);
var eanProducts = await readEANProducts(
selectedHtmlFile, ShopSelector.sKaupat, csvFilesPath);
if (selectedTextFile == null) {
throw Exception('Text file is required for S-Kaupat');
}
var receiptProducts = await readReceiptProducts(selectedTextFile);
var eanProducts =
await readEANProducts(selectedHtmlFile, ShopSelector.sKaupat);

await eanHandler(receiptProducts, eanProducts.toList(), hiveProducts);

receiptProducts2CSV(receiptProducts, csvFilesPath);
eanProducts2CSV(eanProducts, csvFilesPath, ShopSelector.sKaupat.name);
// Export products to csv files
if (exportFilesFormat == ExportFormat.csv.name) {
receiptProducts2CSV(receiptProducts, exportFilesPath);
eanProducts2CSV(
eanProducts, exportFilesPath, ShopSelector.sKaupat.name);
}
// Export products to Excel (xlsx) files
else if (exportFilesFormat == ExportFormat.excel.name) {
receiptProducts2Excel(receiptProducts, exportFilesPath);
eanProducts2Excel(
eanProducts, exportFilesPath, ShopSelector.sKaupat.name);
} else {
print('Unknow export format');
exitCode = 1;
}
} else if (ShopSelector.kRuoka.value == selectedStore) {
var eanProducts = await readEANProducts(
selectedHtmlFile, ShopSelector.kRuoka, csvFilesPath);
var eanProducts =
await readEANProducts(selectedHtmlFile, ShopSelector.kRuoka);

eanProducts2CSV(eanProducts, csvFilesPath, ShopSelector.kRuoka.name);
// Export products to csv file
if (exportFilesFormat == ExportFormat.csv.name) {
eanProducts2CSV(eanProducts, exportFilesPath, ShopSelector.kRuoka.name);
}
// Export products to Excel (xlsx) file
else if (exportFilesFormat == ExportFormat.excel.name) {
eanProducts2Excel(
eanProducts, exportFilesPath, ShopSelector.kRuoka.name);
} else {
print('Unknow export format');
exitCode = 1;
}
} else {
print('Unknown store: $selectedStore');
exitCode = 1;
Expand Down
10 changes: 6 additions & 4 deletions bin/specific/k_ruoka/load_html_k_ruoka.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Future<List<EANProduct>> loadHtmlFromAssets(String filePath) async {
.ceil(); // e.g. 0.2 -> 1 (round up) or 0.5 -> 1 (round up)

eanProducts.add(EANProduct(
ean: productEan,
eanCode: productEan,
name: productName,
quantity: quantity,
totalPrice: productPrice,
Expand Down Expand Up @@ -96,7 +96,7 @@ Future<List<EANProduct>> loadHtmlFromAssets(String filePath) async {
.ceil(); // e.g. 0.2 -> 1 (round up) or 0.5 -> 1 (round up)

eanProducts.add(EANProduct(
ean: eanCode,
eanCode: eanCode,
name: productName,
quantity: quantity,
totalPrice: productPrice,
Expand All @@ -115,7 +115,7 @@ Future<List<EANProduct>> loadHtmlFromAssets(String filePath) async {
var homeDeliveryPrice = homeDeliveryPriceSection.children[1].text;

eanProducts.add(EANProduct(
ean: '',
eanCode: '',
name: homeDeliveryText,
quantity: 1,
totalPrice: homeDeliveryPrice,
Expand All @@ -142,11 +142,13 @@ Future<List<EANProduct>> loadHtmlFromAssets(String filePath) async {
.trim();

eanProducts.add(EANProduct(
ean: '',
eanCode: '',
name: packagingMaterialTerm,
quantity: -1,
totalPrice: '',
pricePerUnit: packagingMaterialPrice,
moreDetails:
'TODO: fill in the amount of packaging material and the total price.',
));

return eanProducts;
Expand Down
4 changes: 2 additions & 2 deletions bin/specific/s_kaupat/ean_handler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ Future<void> _handleFoundCases(
print(greenPen().write('\tFound one product:'));
print('\t\t${filteredEanProducts.first}');

receiptProduct.eanCode = filteredEanProducts.first.ean;
receiptProduct.eanCode = filteredEanProducts.first.eanCode;
} else if (filteredEanProducts.length > 1) {
print(redPen()
.write('\tFound multiple products (${filteredEanProducts.length}'
Expand All @@ -147,7 +147,7 @@ Future<void> _handleFoundCases(

print(greenPen().write('\tYou selected: $selectedEanProduct'));

receiptProduct.eanCode = selectedEanProduct.ean;
receiptProduct.eanCode = selectedEanProduct.eanCode;

await hiveProducts.add(HiveProduct(
receiptName: receiptProduct.name, eanName: selectedEanProduct.name));
Expand Down
2 changes: 1 addition & 1 deletion bin/specific/s_kaupat/load_html_s_kaupat.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Future<List<EANProduct>> loadHtmlFromAssets(String filePath) async {

eanProducts.add(
EANProduct(
ean: eanCode,
eanCode: eanCode,
name: productName,
quantity: quantity,
totalPrice: productPrice,
Expand Down
2 changes: 1 addition & 1 deletion bin/specific/s_kaupat/read_receipt_products.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import '../../models/receipt_product.dart';
import 'strings_to_receipt_products.dart';

Future<List<ReceiptProduct>> readReceiptProducts(
String filePath, String csvFilePath) async {
String filePath) async {
var lines = await readReceiptFile(filePath);
return strings2ReceiptProducts(lines ?? []);
}
Expand Down
9 changes: 7 additions & 2 deletions bin/specific/s_kaupat/receipt_products_2_csv.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import 'dart:io';

import 'package:path/path.dart' as p;

import '../../utils/date_helper.dart';
import '../../models/receipt_product.dart';
import '../../utils/home_directory_helper.dart';

/// Saves receipt products as a CSV file.
void receiptProducts2CSV(List<ReceiptProduct> products, String csvFilePath) {
void receiptProducts2CSV(List<ReceiptProduct> products, String exportFilePath) {
var csv = StringBuffer();
var header = '';
var discountCounted = false;
Expand All @@ -26,6 +30,7 @@ void receiptProducts2CSV(List<ReceiptProduct> products, String csvFilePath) {
'${product.name};${product.quantity};${product.pricePerUnit};${product.totalPrice}${discountCounted ? ';${product.discountCounted}' : ''};${product.eanCode}\n');
}

var file = File('$csvFilePath/receipt_products_${formattedDateTime()}.csv');
var file = File(p.join(replaceTildeWithHomeDirectory(exportFilePath),
'receipt_products_${formattedDateTime()}.csv'));
file.writeAsString(csv.toString());
}
Loading

0 comments on commit 4e1b9fd

Please sign in to comment.