diff --git a/notebooks/test-compose-email.ipynb b/notebooks/test-compose-email.ipynb deleted file mode 100644 index a1a6d92e..00000000 --- a/notebooks/test-compose-email.ipynb +++ /dev/null @@ -1,69 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "from tuttle import os_functions" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "message_attachment = 0\n" - ] - } - ], - "source": [ - "os_functions.compose_email_in_app(\n", - " recipient=\"mail@clstaudt.me\",\n", - " subject=\"Test\",\n", - " body=\"Test\",\n", - " attachment_path=\"/Users/cls/Stack/monalisa.jpeg\"\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3.10.5 ('tuttle-flet')", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.5" - }, - "orig_nbformat": 4, - "vscode": { - "interpreter": { - "hash": "5727a89f8f4826e6ce3fb9da18a08727b274ba76880924157270e222bba4a1ac" - } - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/notebooks/walkthrough/00-introduction.ipynb b/notebooks/walkthrough/00-introduction.ipynb deleted file mode 100644 index 257275b2..00000000 --- a/notebooks/walkthrough/00-introduction.ipynb +++ /dev/null @@ -1,191 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "08f11bab-4bc1-43ed-a59a-31d9b59d4211", - "metadata": {}, - "source": [ - "# Introduction" - ] - }, - { - "cell_type": "markdown", - "id": "2ee6e2c4-7bc6-47bc-8ed1-6938901bd7f7", - "metadata": {}, - "source": [ - "## tuttle - painless business planning for freelancers\n", - "\n", - "> HARRY TUTTLE: Bloody paperwork. Huh!\n", - ">\n", - "> SAM LOWRY: I suppose one has to expect a certain amount.\n", - ">\n", - "> HARRY TUTTLE: Why? I came into this game for the action, the excitement. Go anywhere, travel light, get in, get out, wherever there's trouble, a man alone.\n", - "\n", - "\n", - "**Tuttle is an open-source software project supported by the [Prototype Fund](https://prototypefund.de/en/about-2/). We develop a finance and business planning tool tailored for the requirements of freelancers.**" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "d8c33175-64fa-4655-8f0f-fa2ab59f433d", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from IPython.display import HTML\n", - "\n", - "HTML('')" - ] - }, - { - "cell_type": "markdown", - "id": "c9a8a7b6-e151-4d41-b14b-1fa5cff48fb9", - "metadata": {}, - "source": [ - "### Which challenges does the project address?\n", - "\n", - "The working world is changing, the trend is towards freelancing: software developers, designers and journalists appreciate the freedom and creative possibilities of solo self-employment. More and more professionals are choosing it for themselves. It allows them to specialize and gain experience with many projects and clients.\n", - "\n", - "With freelancing, there are many side activities: Marketing, client communication, legal and financial planning - although the latter probably appeals to few solo self-employed people. But neglected financial planning carries the risk of insolvency, debt, precarious self-employment, or poverty in old age. This also creates burdens for the social systems.\n", - "\n", - "But what if software could make financial planning in freelancing almost as easy as being an employee? Our tool minimizes risks and makes the financial part of the job easy. Freelancing becomes more efficient, less risky, and therefore more beginner-friendly.\n", - "\n", - "### How are you tackling the problem?\n", - "\n", - "With Tuttle, we are developing a financial planning tool that is tailored to the needs of solo freelancers. We automate and give freelancers more time to do the work they love.\n", - "The application provides analysis and forecasting functions on income, expenses, disposable income, uncertainty management or explainability of the forecast and convinces with portability, among other things.\n", - "\n", - "We develop the solution as a GUI application based on web technologies. Sensitive financial data is processed locally on the end device without central data collection. For data analysis, we rely on open source tools from the Python ecosystem.\n", - "\n", - "\n", - "### What is the product vision?\n", - "\n", - "Desktop apps are great - let's have more of them. We are consciously developing a desktop app with local data storage, not a web app, since your business data is none of our business.\n", - "\n", - "For this purpose, the Tuttle project is split across several repositories:\n", - "\n", - "- This repository contains the core library, written in Python.\n", - "- The desktop application frontend, based on Electron.js, is being developed in [tuttle-dev/tuttle-app](https://github.com/tuttle-dev/tuttle-app)\n", - "\n", - "\n", - "### Setup\n", - "\n", - "1. Clone or download the current version from the `main` branch.\n", - "\n", - "2. We recommend installation into a new [virtual environment](https://calmcode.io/virtualenv/intro.html).\n", - "\n", - "3. Install the Python module in development mode:\n", - "\n", - "```shell\n", - "$ python setup.py develop\n", - "```\n", - "\n", - "4. To verify, run the unit tests:\n", - "\n", - "```shell\n", - "$ pytest\n", - "```\n", - "\n", - "\n", - "### Contributing\n", - "\n", - "Your contributions are welcome. Please follow the [guide (CONTRIBUTING.md)](https://github.com/tuttle-dev/tuttle/blob/main/CONTRIBUTING.md).\n", - "\n", - "[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)\n", - "\n", - "\n", - "\n", - "### Acknowledgements\n", - "\n", - "This project is funded by the [Prototype Fund](https://prototypefund.de).\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "id": "9386280c-54af-41a8-bbf6-1f2307f483c9", - "metadata": {}, - "source": [ - "## Privacy by design - your business data is none of our business" - ] - }, - { - "cell_type": "markdown", - "id": "ebb4e3ae-cb0e-4a06-8713-8a3072b1bbec", - "metadata": {}, - "source": [ - "To try out Tuttle, we will ask you to enter data about your freelance business, your clients, your contractual terms, and more. If you say that this data is none of our business and you would rather keep it private, we are the first ones to agree: The data you enter is stored and processed locally, and will not be uploaded to any server. Therefore, we hope that you are comfortable with entering real business data for this demonstration, since this will give you the most useful results and enables you to give the best feedback.\n", - "\n", - "Furthermore, whenever Tuttle asks you to enter a password (e.g. for a cloud account), it will be processed securely and not stored." - ] - }, - { - "cell_type": "markdown", - "id": "bce5e1c0-9609-423e-8f8f-6d55d1b17453", - "metadata": {}, - "source": [ - "## Feature Preview" - ] - }, - { - "cell_type": "markdown", - "id": "6e3e8e94-d7f8-4235-b5ad-5a9ecd4f819d", - "metadata": {}, - "source": [ - "- **track your work time with ease**\n", - "\n", - "- **automate your invoicing**\n", - "\n", - "- **plan ahead and forecast your revenue**\n", - "\n", - "- **forecast revenue-dependent expenses (under construction)**\n", - " \n", - "- **calculate your effective income (under construction)**\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "id": "cf448e40-28df-4b2e-a244-c65f9431c5c4", - "metadata": {}, - "source": [ - "------" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "tuttle", - "language": "python", - "name": "ex" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.6" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/walkthrough/01-user-setup.ipynb b/notebooks/walkthrough/01-user-setup.ipynb deleted file mode 100644 index d976f519..00000000 --- a/notebooks/walkthrough/01-user-setup.ipynb +++ /dev/null @@ -1,563 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "0acaba00", - "metadata": {}, - "source": [ - "# User Setup" - ] - }, - { - "cell_type": "markdown", - "id": "5d00d55d", - "metadata": {}, - "source": [ - "## Preamble" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "da860d61", - "metadata": {}, - "outputs": [], - "source": [ - "from pathlib import Path" - ] - }, - { - "cell_type": "markdown", - "id": "550d0f54", - "metadata": {}, - "source": [ - "## Setup" - ] - }, - { - "cell_type": "markdown", - "id": "e00af4ce", - "metadata": {}, - "source": [ - "As the first step, we import the `tuttle` Python module and instantiate the `app` object:" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "e18e69f9", - "metadata": {}, - "outputs": [], - "source": [ - "import tuttle" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "8c7d2bab", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2022-11-01 16:26:08.736 | INFO | tuttle.controller:create_model:76 - creating database model\n" - ] - } - ], - "source": [ - "controller = tuttle.controller.Controller(\n", - " preferences=tuttle.preferences.Preferences(\n", - " invoice_dir=Path(\"Invoices\"),\n", - " ),\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "e5546c95", - "metadata": {}, - "source": [ - "Since this is the beginning of the workflow, let's make sure that we start from a clean database." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "015603fd", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2022-11-01 16:26:08.744 | INFO | tuttle.controller:create_model:76 - creating database model\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2022-11-01 16:26:08,745 INFO sqlalchemy.engine.Engine BEGIN (implicit)\n", - "2022-11-01 16:26:08,746 INFO sqlalchemy.engine.Engine PRAGMA main.table_info(\"address\")\n", - "2022-11-01 16:26:08,746 INFO sqlalchemy.engine.Engine [raw sql] ()\n", - "2022-11-01 16:26:08,747 INFO sqlalchemy.engine.Engine PRAGMA temp.table_info(\"address\")\n", - "2022-11-01 16:26:08,747 INFO sqlalchemy.engine.Engine [raw sql] ()\n", - "2022-11-01 16:26:08,747 INFO sqlalchemy.engine.Engine PRAGMA main.table_info(\"user\")\n", - "2022-11-01 16:26:08,748 INFO sqlalchemy.engine.Engine [raw sql] ()\n", - "2022-11-01 16:26:08,748 INFO sqlalchemy.engine.Engine PRAGMA temp.table_info(\"user\")\n", - "2022-11-01 16:26:08,748 INFO sqlalchemy.engine.Engine [raw sql] ()\n", - "2022-11-01 16:26:08,749 INFO sqlalchemy.engine.Engine PRAGMA main.table_info(\"icloudaccount\")\n", - "2022-11-01 16:26:08,749 INFO sqlalchemy.engine.Engine [raw sql] ()\n", - "2022-11-01 16:26:08,749 INFO sqlalchemy.engine.Engine PRAGMA temp.table_info(\"icloudaccount\")\n", - "2022-11-01 16:26:08,750 INFO sqlalchemy.engine.Engine [raw sql] ()\n", - "2022-11-01 16:26:08,750 INFO sqlalchemy.engine.Engine PRAGMA main.table_info(\"googleaccount\")\n", - "2022-11-01 16:26:08,750 INFO sqlalchemy.engine.Engine [raw sql] ()\n", - "2022-11-01 16:26:08,751 INFO sqlalchemy.engine.Engine PRAGMA temp.table_info(\"googleaccount\")\n", - "2022-11-01 16:26:08,751 INFO sqlalchemy.engine.Engine [raw sql] ()\n", - "2022-11-01 16:26:08,752 INFO sqlalchemy.engine.Engine PRAGMA main.table_info(\"bank\")\n", - "2022-11-01 16:26:08,752 INFO sqlalchemy.engine.Engine [raw sql] ()\n", - "2022-11-01 16:26:08,752 INFO sqlalchemy.engine.Engine PRAGMA temp.table_info(\"bank\")\n", - "2022-11-01 16:26:08,752 INFO sqlalchemy.engine.Engine [raw sql] ()\n", - "2022-11-01 16:26:08,753 INFO sqlalchemy.engine.Engine PRAGMA main.table_info(\"bankaccount\")\n", - "2022-11-01 16:26:08,753 INFO sqlalchemy.engine.Engine [raw sql] ()\n", - "2022-11-01 16:26:08,754 INFO sqlalchemy.engine.Engine PRAGMA temp.table_info(\"bankaccount\")\n", - "2022-11-01 16:26:08,754 INFO sqlalchemy.engine.Engine [raw sql] ()\n", - "2022-11-01 16:26:08,755 INFO sqlalchemy.engine.Engine PRAGMA main.table_info(\"contact\")\n", - "2022-11-01 16:26:08,755 INFO sqlalchemy.engine.Engine [raw sql] ()\n", - "2022-11-01 16:26:08,755 INFO sqlalchemy.engine.Engine PRAGMA temp.table_info(\"contact\")\n", - "2022-11-01 16:26:08,755 INFO sqlalchemy.engine.Engine [raw sql] ()\n", - "2022-11-01 16:26:08,756 INFO sqlalchemy.engine.Engine PRAGMA main.table_info(\"client\")\n", - "2022-11-01 16:26:08,756 INFO sqlalchemy.engine.Engine [raw sql] ()\n", - "2022-11-01 16:26:08,757 INFO sqlalchemy.engine.Engine PRAGMA temp.table_info(\"client\")\n", - "2022-11-01 16:26:08,757 INFO sqlalchemy.engine.Engine [raw sql] ()\n", - "2022-11-01 16:26:08,757 INFO sqlalchemy.engine.Engine PRAGMA main.table_info(\"contract\")\n", - "2022-11-01 16:26:08,757 INFO sqlalchemy.engine.Engine [raw sql] ()\n", - "2022-11-01 16:26:08,758 INFO sqlalchemy.engine.Engine PRAGMA temp.table_info(\"contract\")\n", - "2022-11-01 16:26:08,758 INFO sqlalchemy.engine.Engine [raw sql] ()\n", - "2022-11-01 16:26:08,758 INFO sqlalchemy.engine.Engine PRAGMA main.table_info(\"project\")\n", - "2022-11-01 16:26:08,759 INFO sqlalchemy.engine.Engine [raw sql] ()\n", - "2022-11-01 16:26:08,759 INFO sqlalchemy.engine.Engine PRAGMA temp.table_info(\"project\")\n", - "2022-11-01 16:26:08,759 INFO sqlalchemy.engine.Engine [raw sql] ()\n", - "2022-11-01 16:26:08,760 INFO sqlalchemy.engine.Engine PRAGMA main.table_info(\"timetrackingitem\")\n", - "2022-11-01 16:26:08,760 INFO sqlalchemy.engine.Engine [raw sql] ()\n", - "2022-11-01 16:26:08,760 INFO sqlalchemy.engine.Engine PRAGMA temp.table_info(\"timetrackingitem\")\n", - "2022-11-01 16:26:08,761 INFO sqlalchemy.engine.Engine [raw sql] ()\n", - "2022-11-01 16:26:08,761 INFO sqlalchemy.engine.Engine PRAGMA main.table_info(\"timesheet\")\n", - "2022-11-01 16:26:08,761 INFO sqlalchemy.engine.Engine [raw sql] ()\n", - "2022-11-01 16:26:08,762 INFO sqlalchemy.engine.Engine PRAGMA temp.table_info(\"timesheet\")\n", - "2022-11-01 16:26:08,762 INFO sqlalchemy.engine.Engine [raw sql] ()\n", - "2022-11-01 16:26:08,763 INFO sqlalchemy.engine.Engine PRAGMA main.table_info(\"invoice\")\n", - "2022-11-01 16:26:08,763 INFO sqlalchemy.engine.Engine [raw sql] ()\n", - "2022-11-01 16:26:08,764 INFO sqlalchemy.engine.Engine PRAGMA temp.table_info(\"invoice\")\n", - "2022-11-01 16:26:08,765 INFO sqlalchemy.engine.Engine [raw sql] ()\n", - "2022-11-01 16:26:08,766 INFO sqlalchemy.engine.Engine PRAGMA main.table_info(\"invoiceitem\")\n", - "2022-11-01 16:26:08,766 INFO sqlalchemy.engine.Engine [raw sql] ()\n", - "2022-11-01 16:26:08,766 INFO sqlalchemy.engine.Engine PRAGMA temp.table_info(\"invoiceitem\")\n", - "2022-11-01 16:26:08,767 INFO sqlalchemy.engine.Engine [raw sql] ()\n", - "2022-11-01 16:26:08,767 INFO sqlalchemy.engine.Engine PRAGMA main.table_info(\"timelineitem\")\n", - "2022-11-01 16:26:08,767 INFO sqlalchemy.engine.Engine [raw sql] ()\n", - "2022-11-01 16:26:08,768 INFO sqlalchemy.engine.Engine PRAGMA temp.table_info(\"timelineitem\")\n", - "2022-11-01 16:26:08,768 INFO sqlalchemy.engine.Engine [raw sql] ()\n", - "2022-11-01 16:26:08,769 INFO sqlalchemy.engine.Engine \n", - "CREATE TABLE address (\n", - "\tid INTEGER NOT NULL, \n", - "\tstreet VARCHAR NOT NULL, \n", - "\tnumber VARCHAR NOT NULL, \n", - "\tcity VARCHAR NOT NULL, \n", - "\tpostal_code VARCHAR NOT NULL, \n", - "\tcountry VARCHAR NOT NULL, \n", - "\tPRIMARY KEY (id)\n", - ")\n", - "\n", - "\n", - "2022-11-01 16:26:08,770 INFO sqlalchemy.engine.Engine [no key 0.00049s] ()\n", - "2022-11-01 16:26:08,771 INFO sqlalchemy.engine.Engine \n", - "CREATE TABLE icloudaccount (\n", - "\tid INTEGER NOT NULL, \n", - "\tuser_name VARCHAR NOT NULL, \n", - "\tPRIMARY KEY (id)\n", - ")\n", - "\n", - "\n", - "2022-11-01 16:26:08,772 INFO sqlalchemy.engine.Engine [no key 0.00041s] ()\n", - "2022-11-01 16:26:08,773 INFO sqlalchemy.engine.Engine \n", - "CREATE TABLE googleaccount (\n", - "\tid INTEGER NOT NULL, \n", - "\tuser_name VARCHAR NOT NULL, \n", - "\tPRIMARY KEY (id)\n", - ")\n", - "\n", - "\n", - "2022-11-01 16:26:08,773 INFO sqlalchemy.engine.Engine [no key 0.00035s] ()\n", - "2022-11-01 16:26:08,774 INFO sqlalchemy.engine.Engine \n", - "CREATE TABLE bank (\n", - "\tid INTEGER NOT NULL, \n", - "\t\"BLZ\" VARCHAR NOT NULL, \n", - "\tPRIMARY KEY (id)\n", - ")\n", - "\n", - "\n", - "2022-11-01 16:26:08,775 INFO sqlalchemy.engine.Engine [no key 0.00037s] ()\n", - "2022-11-01 16:26:08,776 INFO sqlalchemy.engine.Engine \n", - "CREATE TABLE bankaccount (\n", - "\tid INTEGER NOT NULL, \n", - "\tname VARCHAR NOT NULL, \n", - "\t\"IBAN\" VARCHAR NOT NULL, \n", - "\t\"BIC\" VARCHAR NOT NULL, \n", - "\tPRIMARY KEY (id)\n", - ")\n", - "\n", - "\n", - "2022-11-01 16:26:08,777 INFO sqlalchemy.engine.Engine [no key 0.00046s] ()\n", - "2022-11-01 16:26:08,778 INFO sqlalchemy.engine.Engine \n", - "CREATE TABLE timelineitem (\n", - "\ttime DATETIME NOT NULL, \n", - "\tid INTEGER NOT NULL, \n", - "\tcontent VARCHAR NOT NULL, \n", - "\tPRIMARY KEY (id)\n", - ")\n", - "\n", - "\n", - "2022-11-01 16:26:08,779 INFO sqlalchemy.engine.Engine [no key 0.00040s] ()\n", - "2022-11-01 16:26:08,780 INFO sqlalchemy.engine.Engine \n", - "CREATE TABLE user (\n", - "\tid INTEGER NOT NULL, \n", - "\tname VARCHAR NOT NULL, \n", - "\tsubtitle VARCHAR NOT NULL, \n", - "\twebsite VARCHAR NOT NULL, \n", - "\temail VARCHAR NOT NULL, \n", - "\tphone_number VARCHAR NOT NULL, \n", - "\taddress_id INTEGER, \n", - "\t\"VAT_number\" VARCHAR, \n", - "\ticloud_account_id INTEGER, \n", - "\tbank_account_id INTEGER, \n", - "\tlogo VARCHAR, \n", - "\tPRIMARY KEY (id), \n", - "\tFOREIGN KEY(address_id) REFERENCES address (id), \n", - "\tFOREIGN KEY(icloud_account_id) REFERENCES icloudaccount (id), \n", - "\tFOREIGN KEY(bank_account_id) REFERENCES bankaccount (id)\n", - ")\n", - "\n", - "\n", - "2022-11-01 16:26:08,781 INFO sqlalchemy.engine.Engine [no key 0.00055s] ()\n", - "2022-11-01 16:26:08,782 INFO sqlalchemy.engine.Engine \n", - "CREATE TABLE contact (\n", - "\tid INTEGER NOT NULL, \n", - "\tfirst_name VARCHAR, \n", - "\tlast_name VARCHAR, \n", - "\tcompany VARCHAR, \n", - "\temail VARCHAR, \n", - "\taddress_id INTEGER, \n", - "\tPRIMARY KEY (id), \n", - "\tFOREIGN KEY(address_id) REFERENCES address (id)\n", - ")\n", - "\n", - "\n", - "2022-11-01 16:26:08,783 INFO sqlalchemy.engine.Engine [no key 0.00071s] ()\n", - "2022-11-01 16:26:08,784 INFO sqlalchemy.engine.Engine \n", - "CREATE TABLE client (\n", - "\tid INTEGER NOT NULL, \n", - "\tname VARCHAR NOT NULL, \n", - "\tinvoicing_contact_id INTEGER, \n", - "\tPRIMARY KEY (id), \n", - "\tFOREIGN KEY(invoicing_contact_id) REFERENCES contact (id)\n", - ")\n", - "\n", - "\n", - "2022-11-01 16:26:08,785 INFO sqlalchemy.engine.Engine [no key 0.00039s] ()\n", - "2022-11-01 16:26:08,787 INFO sqlalchemy.engine.Engine \n", - "CREATE TABLE contract (\n", - "\tunit VARCHAR(6), \n", - "\tbilling_cycle VARCHAR(9), \n", - "\tid INTEGER NOT NULL, \n", - "\ttitle VARCHAR NOT NULL, \n", - "\tsignature_date DATE NOT NULL, \n", - "\tstart_date DATE NOT NULL, \n", - "\tend_date DATE, \n", - "\tclient_id INTEGER, \n", - "\trate NUMERIC NOT NULL, \n", - "\tcurrency VARCHAR NOT NULL, \n", - "\t\"VAT_rate\" NUMERIC NOT NULL, \n", - "\tunits_per_workday INTEGER NOT NULL, \n", - "\tvolume INTEGER, \n", - "\tterm_of_payment INTEGER, \n", - "\tPRIMARY KEY (id), \n", - "\tFOREIGN KEY(client_id) REFERENCES client (id)\n", - ")\n", - "\n", - "\n", - "2022-11-01 16:26:08,788 INFO sqlalchemy.engine.Engine [no key 0.00076s] ()\n", - "2022-11-01 16:26:08,790 INFO sqlalchemy.engine.Engine \n", - "CREATE TABLE project (\n", - "\tid INTEGER NOT NULL, \n", - "\ttitle VARCHAR NOT NULL, \n", - "\ttag VARCHAR NOT NULL, \n", - "\tstart_date DATE NOT NULL, \n", - "\tend_date DATE NOT NULL, \n", - "\tcontract_id INTEGER, \n", - "\tPRIMARY KEY (id), \n", - "\tUNIQUE (title), \n", - "\tUNIQUE (tag), \n", - "\tFOREIGN KEY(contract_id) REFERENCES contract (id)\n", - ")\n", - "\n", - "\n", - "2022-11-01 16:26:08,790 INFO sqlalchemy.engine.Engine [no key 0.00037s] ()\n", - "2022-11-01 16:26:08,792 INFO sqlalchemy.engine.Engine \n", - "CREATE TABLE invoice (\n", - "\tid INTEGER NOT NULL, \n", - "\tnumber VARCHAR, \n", - "\tdate DATE NOT NULL, \n", - "\tcontract_id INTEGER, \n", - "\tsent BOOLEAN, \n", - "\tpaid BOOLEAN, \n", - "\tcancelled BOOLEAN, \n", - "\trendered BOOLEAN NOT NULL, \n", - "\tPRIMARY KEY (id), \n", - "\tFOREIGN KEY(contract_id) REFERENCES contract (id)\n", - ")\n", - "\n", - "\n", - "2022-11-01 16:26:08,792 INFO sqlalchemy.engine.Engine [no key 0.00050s] ()\n", - "2022-11-01 16:26:08,794 INFO sqlalchemy.engine.Engine \n", - "CREATE TABLE timesheet (\n", - "\tid INTEGER NOT NULL, \n", - "\ttitle VARCHAR NOT NULL, \n", - "\tdate DATE NOT NULL, \n", - "\tproject_id INTEGER, \n", - "\tcomment VARCHAR, \n", - "\tPRIMARY KEY (id), \n", - "\tFOREIGN KEY(project_id) REFERENCES project (id)\n", - ")\n", - "\n", - "\n", - "2022-11-01 16:26:08,794 INFO sqlalchemy.engine.Engine [no key 0.00049s] ()\n", - "2022-11-01 16:26:08,796 INFO sqlalchemy.engine.Engine \n", - "CREATE TABLE invoiceitem (\n", - "\tid INTEGER NOT NULL, \n", - "\tstart_date DATE NOT NULL, \n", - "\tend_date DATE, \n", - "\tquantity INTEGER NOT NULL, \n", - "\tunit VARCHAR NOT NULL, \n", - "\tunit_price NUMERIC NOT NULL, \n", - "\tdescription VARCHAR NOT NULL, \n", - "\t\"VAT_rate\" NUMERIC NOT NULL, \n", - "\tinvoice_id INTEGER, \n", - "\tPRIMARY KEY (id), \n", - "\tFOREIGN KEY(invoice_id) REFERENCES invoice (id)\n", - ")\n", - "\n", - "\n", - "2022-11-01 16:26:08,796 INFO sqlalchemy.engine.Engine [no key 0.00038s] ()\n", - "2022-11-01 16:26:08,797 INFO sqlalchemy.engine.Engine \n", - "CREATE TABLE timetrackingitem (\n", - "\tid INTEGER NOT NULL, \n", - "\ttimesheet_id INTEGER, \n", - "\t\"begin\" DATETIME NOT NULL, \n", - "\t\"end\" DATETIME, \n", - "\tduration DATETIME NOT NULL, \n", - "\ttitle VARCHAR NOT NULL, \n", - "\ttag VARCHAR NOT NULL, \n", - "\tdescription VARCHAR, \n", - "\tPRIMARY KEY (id), \n", - "\tFOREIGN KEY(timesheet_id) REFERENCES timesheet (id)\n", - ")\n", - "\n", - "\n", - "2022-11-01 16:26:08,798 INFO sqlalchemy.engine.Engine [no key 0.00028s] ()\n", - "2022-11-01 16:26:08,799 INFO sqlalchemy.engine.Engine COMMIT\n" - ] - } - ], - "source": [ - "controller.clear_database()" - ] - }, - { - "cell_type": "markdown", - "id": "76147ed9", - "metadata": {}, - "source": [ - "After running this, a new database has been created in your user space:" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "4f92fb57", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[34mInvoices\u001b[m\u001b[m tuttle.db\n" - ] - } - ], - "source": [ - "!ls {Path.home()}/.tuttle/" - ] - }, - { - "cell_type": "markdown", - "id": "c164a85d", - "metadata": {}, - "source": [ - "## User" - ] - }, - { - "cell_type": "markdown", - "id": "3207f95a", - "metadata": {}, - "source": [ - "When first running the application, you need to provide the application with information about you and your freelance business. You do this by creating a new `User` object." - ] - }, - { - "cell_type": "markdown", - "id": "ea305178", - "metadata": {}, - "source": [ - "Here is an example:" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "6ec17c00", - "metadata": {}, - "outputs": [], - "source": [ - "from tuttle.model import User, Address, BankAccount, ICloudAccount" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "d91df78a", - "metadata": {}, - "outputs": [], - "source": [ - "harry = User(\n", - " name=\"Harry Tuttle\",\n", - " subtitle=\"Heating Engineer\",\n", - " website=\"https://tuttle-dev.github.io/tuttle/\",\n", - " email=\"mail@tuttle.com\",\n", - " phone_number=\"+55555555555\",\n", - " VAT_number=\"DZ-015\",\n", - " address=Address(\n", - " name=\"Harry Tuttle\",\n", - " street=\"Main Street\",\n", - " number=\"450\",\n", - " city=\"Sao Paolo\",\n", - " postal_code=\"555555\",\n", - " country=\"Brazil\",\n", - " ),\n", - " bank_account=BankAccount(\n", - " name=\"Giro\",\n", - " IBAN=\"BZ99830994950003161565\",\n", - " BIC=\"ABCDEFGH\",\n", - " ),\n", - " icloud_account=ICloudAccount(user_name=\"mail@tuttle.com\")\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "65edff5d", - "metadata": {}, - "source": [ - "Now it's your turn - please provide your details and instantiate a user object. \n", - "\n", - "(If you do not have an iCloud account, don't worry, there are other options of accessing your calendar data)." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "df42fed8", - "metadata": {}, - "outputs": [], - "source": [ - "user = harry # replace this with an new user object" - ] - }, - { - "cell_type": "markdown", - "id": "02d4f3d1", - "metadata": {}, - "source": [ - "Store your new user in the database:" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "004f71ec", - "metadata": {}, - "outputs": [], - "source": [ - "controller.store(user)" - ] - }, - { - "cell_type": "markdown", - "id": "bcc1ab33", - "metadata": {}, - "source": [ - "Test this by accessing the user:" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "77647ac7", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "User(id=1, website='https://tuttle-dev.github.io/tuttle/', phone_number='+55555555555', VAT_number='DZ-015', bank_account_id=1, name='Harry Tuttle', subtitle='Heating Engineer', email='mail@tuttle.com', address_id=1, icloud_account_id=1, logo=None)" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "controller.user" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "tuttle", - "language": "python", - "name": "ex" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.6" - }, - "vscode": { - "interpreter": { - "hash": "5727a89f8f4826e6ce3fb9da18a08727b274ba76880924157270e222bba4a1ac" - } - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/walkthrough/02-projects-setup.ipynb b/notebooks/walkthrough/02-projects-setup.ipynb deleted file mode 100644 index 80006f4b..00000000 --- a/notebooks/walkthrough/02-projects-setup.ipynb +++ /dev/null @@ -1,541 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "d90ff354", - "metadata": {}, - "source": [ - "# Setup: Projects" - ] - }, - { - "cell_type": "markdown", - "id": "ca3d885e", - "metadata": {}, - "source": [ - "As a freelancer, you work on _projects_: You agree with a _client_ to perform a certain amount of work. The terms of your work relationship, including terms of payment, are recorded in a _contract_.\n", - "\n", - "Let's setup at least one project." - ] - }, - { - "cell_type": "markdown", - "id": "4d9df793", - "metadata": {}, - "source": [ - "## Preamble" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "73bd1f9b", - "metadata": {}, - "outputs": [], - "source": [ - "import datetime\n", - "from pathlib import Path" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "c05e4eeb", - "metadata": {}, - "outputs": [], - "source": [ - "import tuttle" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "8ce61d54", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2022-11-01 16:27:02.447 | INFO | tuttle.controller:create_model:76 - creating database model\n" - ] - } - ], - "source": [ - "controller = tuttle.controller.Controller(\n", - " preferences=tuttle.preferences.Preferences(\n", - " invoice_dir=Path(\"Invoices\"),\n", - " ),\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "d0b838c9", - "metadata": {}, - "source": [ - "### Setup: Clients" - ] - }, - { - "cell_type": "markdown", - "id": "1bd82ca3", - "metadata": {}, - "source": [ - "The `Client` is the person or business you are working for in the course of a project." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "638ac382", - "metadata": {}, - "outputs": [], - "source": [ - "from tuttle.model import Client, Contact, Address" - ] - }, - { - "cell_type": "markdown", - "id": "3c2afcc2", - "metadata": {}, - "source": [ - "**Example Client**" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "8ac99de1", - "metadata": {}, - "outputs": [], - "source": [ - "sam = Client(\n", - " name=\"Sam Lowry\",\n", - " invoicing_contact=Contact(\n", - " name=\"Sam Lowry\",\n", - " e_mail=\"mail@lowry.com\",\n", - " address=Address(\n", - " street=\"Main Street\",\n", - " number=\"9999\",\n", - " postal_code=\"55555\",\n", - " city=\"Sao Paolo\",\n", - " country=\"Brazil\",\n", - " ),\n", - " ),\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "4bb09a4a", - "metadata": { - "tags": [] - }, - "source": [ - "_Your turn: Enter and store the data for at least one of your clients._" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "756d9905", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "id": "3b716d26", - "metadata": {}, - "source": [ - "## Setup: Contracts" - ] - }, - { - "cell_type": "markdown", - "id": "f232471b", - "metadata": {}, - "source": [ - "When you agree with a client on the business terms of a project, you record them in a contract signed by both parties. In `tuttle`, this is what the `Contract` object is for." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "c553ec2c", - "metadata": {}, - "outputs": [], - "source": [ - "from tuttle.model import Contract" - ] - }, - { - "cell_type": "markdown", - "id": "ce609c7a", - "metadata": {}, - "source": [ - "**Example Contracts**" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "98b1fb3d", - "metadata": {}, - "outputs": [], - "source": [ - "heating_repair_contract = Contract(\n", - " title=\"Heating Repair Contract\",\n", - " client=sam,\n", - " rate=50.00,\n", - " currency=\"EUR\",\n", - " unit=tuttle.time.TimeUnit.hour,\n", - " units_per_workday=8, \n", - " term_of_payment=14,\n", - " billing_cycle=tuttle.time.Cycle.monthly,\n", - " signature_date=datetime.date(2022, 1, 1),\n", - " start_date=datetime.date(2022, 1, 1),\n", - " end_date=datetime.date(2022, 12, 31),\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "0c4190a4", - "metadata": {}, - "outputs": [], - "source": [ - "heating_engineering_contract = Contract(\n", - " title=\"Heating Repair Contract\",\n", - " client=sam,\n", - " rate=100.00,\n", - " currency=\"EUR\",\n", - " unit=tuttle.time.TimeUnit.hour,\n", - " units_per_workday=8, \n", - " term_of_payment=30,\n", - " billing_cycle=tuttle.time.Cycle.monthly,\n", - " signature_date=datetime.date(2022, 1, 1),\n", - " start_date=datetime.date(2022, 1, 1),\n", - " end_date=datetime.date(2022, 12, 31),\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "8ae20a7b", - "metadata": {}, - "source": [ - "_Hint: To clarify what the different parameters mean, check out this function:_" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "59832169", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
field namefield description
0idNone
1titleShort description of the contract.
2signature_dateDate on which the contract was signed
3start_dateDate from which the contract is valid
4end_dateDate until which the contract is valid
5client_idNone
6rateRate of remuneration
7currencyNone
8VAT_rateVAT rate applied to the contractual rate.
9unitUnit of time tracked. The rate applies to this...
10units_per_workdayHow many units of time (e.g. hours) constitute...
11volumeNumber of units agreed on
12term_of_paymentHow many days after receipt of invoice this in...
13billing_cycleNone
\n", - "
" - ], - "text/plain": [ - " field name field description\n", - "0 id None\n", - "1 title Short description of the contract.\n", - "2 signature_date Date on which the contract was signed\n", - "3 start_date Date from which the contract is valid\n", - "4 end_date Date until which the contract is valid\n", - "5 client_id None\n", - "6 rate Rate of remuneration\n", - "7 currency None\n", - "8 VAT_rate VAT rate applied to the contractual rate.\n", - "9 unit Unit of time tracked. The rate applies to this...\n", - "10 units_per_workday How many units of time (e.g. hours) constitute...\n", - "11 volume Number of units agreed on\n", - "12 term_of_payment How many days after receipt of invoice this in...\n", - "13 billing_cycle None" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "tuttle.model.help(Contract)" - ] - }, - { - "cell_type": "markdown", - "id": "539adb00", - "metadata": { - "tags": [] - }, - "source": [ - "_Your turn: Enter and store the data for at least one contract._" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8dbed25d", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "id": "1aa5f725", - "metadata": {}, - "source": [ - "_Feedback question: Were you able map all the important terms of your freelance contract(s) to the `Contract` object? Did you miss any attribute?_" - ] - }, - { - "cell_type": "markdown", - "id": "c3f9e9b9", - "metadata": {}, - "source": [ - "## Setup: Projects" - ] - }, - { - "cell_type": "markdown", - "id": "6f74727f", - "metadata": {}, - "source": [ - "A `Project` is a large, logical unit of work. Tuttle's automatic invoice generation feature assumes that every invoice is related to one project. You may have several projects with the same client. And since the terms of business can change or get extended, you may have several contracts attached to a project. However, only one contract can be active at a time.\n", - "\n", - "Let's start with the simplest case: One client, one project, one contract." - ] - }, - { - "cell_type": "markdown", - "id": "4f425820", - "metadata": {}, - "source": [ - "**Example Project**" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "fc8cb89a", - "metadata": {}, - "outputs": [], - "source": [ - "import datetime\n", - "\n", - "from tuttle.model import Project" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "f1debba6", - "metadata": {}, - "outputs": [], - "source": [ - "heating_repair = Project(\n", - " title=\"Heating Repair\",\n", - " tag=\"#HeatingRepair\",\n", - " contract=heating_repair_contract,\n", - " start_date=datetime.date(2022, 1, 1),\n", - " end_date=datetime.date(2022, 12, 31),\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "1d313441", - "metadata": {}, - "outputs": [], - "source": [ - "controller.store(heating_repair)" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "276194d5", - "metadata": {}, - "outputs": [], - "source": [ - "heating_engineering = Project(\n", - " title=\"Heating Engineering\",\n", - " tag=\"#HeatingEngineering\",\n", - " contract=heating_repair_contract,\n", - " start_date=datetime.date(2022, 1, 1),\n", - " end_date=datetime.date(2022, 12, 31),\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "fc4c0b63", - "metadata": {}, - "outputs": [], - "source": [ - "controller.store(heating_engineering)" - ] - }, - { - "cell_type": "markdown", - "id": "c9683d34", - "metadata": {}, - "source": [ - "_Hint: The `tag` attribute uniquely identifies the project for time tracking purposes - more about this in the subsequent notebooks._" - ] - }, - { - "cell_type": "markdown", - "id": "ddbc09a4", - "metadata": {}, - "source": [ - "_Your turn: Enter and store the data for at least one project._" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "13742e9c", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "id": "3f8c5f1e", - "metadata": {}, - "source": [ - "----" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "tuttle", - "language": "python", - "name": "ex" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.6" - }, - "vscode": { - "interpreter": { - "hash": "5727a89f8f4826e6ce3fb9da18a08727b274ba76880924157270e222bba4a1ac" - } - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/walkthrough/03-timetracking-invoicing.ipynb b/notebooks/walkthrough/03-timetracking-invoicing.ipynb deleted file mode 100644 index cfb1fc86..00000000 --- a/notebooks/walkthrough/03-timetracking-invoicing.ipynb +++ /dev/null @@ -1,1303 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "fbfb5796", - "metadata": {}, - "source": [ - "# Time Tracking" - ] - }, - { - "cell_type": "markdown", - "id": "038e3a54", - "metadata": {}, - "source": [ - "Our target user is a freelancer who bills clients by the time worked. Therefore, managing, tracking and planning your work time is a central task. Tuttle wants to make this as easy and effective as possible for you. Let's get started:" - ] - }, - { - "cell_type": "markdown", - "id": "ce7b5cce", - "metadata": {}, - "source": [ - "## Preamble" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8d3c6eef", - "metadata": {}, - "outputs": [], - "source": [ - "from pathlib import Path\n", - "import ipywidgets\n", - "from IPython import display\n", - "import datetime" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "a39acfe0", - "metadata": {}, - "outputs": [], - "source": [ - "import tuttle" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "e675cb57", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2022-11-01 16:32:21.159 | INFO | tuttle.controller:create_model:76 - creating database model\n" - ] - } - ], - "source": [ - "controller = tuttle.controller.Controller(\n", - " preferences=tuttle.preferences.Preferences(\n", - " invoice_dir=Path(\"Invoices\"),\n", - " ),\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "7872e86d", - "metadata": {}, - "source": [ - "## How to Record Project Time with Tuttle" - ] - }, - { - "cell_type": "markdown", - "id": "32ce5f27", - "metadata": {}, - "source": [ - "- Previously we asked you to assign a unique **tag** to each project that you want to track. We use this tag to assign time tracking entries to projects, trying to find the tag in:\n", - " - the title of your calendar entries\n", - " - the title of your time tracking entries" - ] - }, - { - "cell_type": "markdown", - "id": "cd38504c", - "metadata": { - "tags": [] - }, - "source": [ - "## Importing Time Tracking Data" - ] - }, - { - "cell_type": "markdown", - "id": "bfe74137", - "metadata": {}, - "source": [ - "Currently we provide several ways of inputting time management data:\n", - " \n", - "1. **Cloud Calendar**: Querying your cloud calendar: Log into your cloud calendar provider and import your calendar events seamlessly.\n", - "2. **File Calendar**: Export your calendar in the iCalendar format and parse it.\n", - "3. **Time Tracking Spreadsheets**: You may prefer to track your time not in the calendar but a specialized tool. As long as this tool can export time tracking data in a consistent format, we want to provide an option to import it into Tuttle." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "70206b6e", - "metadata": {}, - "outputs": [], - "source": [ - "time_tracking_preference = ipywidgets.RadioButtons(options=[\"File Calendar\", \"Cloud Calendar\", \"Spreadsheet\"])" - ] - }, - { - "cell_type": "markdown", - "id": "ce80dd15", - "metadata": {}, - "source": [ - "_Select your prereference for importing time tracking data:_" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "08e2dc62", - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "b186e6967b56412293feb5e1e55cb880", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "RadioButtons(options=('File Calendar', 'Cloud Calendar', 'Spreadsheet'), value='File Calendar')" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "time_tracking_preference" - ] - }, - { - "cell_type": "markdown", - "id": "fa87aafa", - "metadata": { - "tags": [] - }, - "source": [ - "### A) Time Tracking via Cloud Calendar" - ] - }, - { - "cell_type": "markdown", - "id": "63a4aba0", - "metadata": {}, - "source": [ - "If you have an Apple iCloud account, follow this process. (We are working on connectors with other cloud services)" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "07e2e438", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'mail@tuttle.com'" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "icloud_username = controller.user.icloud_account.user_name\n", - "icloud_username" - ] - }, - { - "cell_type": "markdown", - "id": "a931f0dd", - "metadata": {}, - "source": [ - "Connect to your cloud calendar that contains your time tracking events:" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "9bae8ae6", - "metadata": {}, - "outputs": [], - "source": [ - "from tuttle.calendar import ICloudCalendar" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "629c625a", - "metadata": {}, - "outputs": [], - "source": [ - "if time_tracking_preference.value == \"Cloud Calendar\":\n", - " my_calendar = ICloudCalendar(\n", - " icloud=tuttle.cloud.login_iCloud(\n", - " user_name=icloud_username,\n", - " ),\n", - " name=input(\"calendar name: \"),\n", - " )" - ] - }, - { - "cell_type": "markdown", - "id": "0700c550", - "metadata": {}, - "source": [ - "### B) Time Tracking via Calendar Import" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "1eb0abbe", - "metadata": {}, - "outputs": [], - "source": [ - "from tuttle.calendar import FileCalendar" - ] - }, - { - "cell_type": "markdown", - "id": "53246ec5", - "metadata": {}, - "source": [ - "**Example: file calendar**" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "a5068311", - "metadata": {}, - "outputs": [], - "source": [ - "timetracking_calendar_path = Path(\"../../tuttle_tests/data/TuttleDemo-TimeTracking.ics\")" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "65a7b6d9", - "metadata": {}, - "outputs": [], - "source": [ - "if time_tracking_preference.value == \"File Calendar\":\n", - " my_calendar = FileCalendar(\n", - " path=timetracking_calendar_path, \n", - " name=\"TimeTracking\"\n", - " )" - ] - }, - { - "cell_type": "markdown", - "id": "ca36ee06", - "metadata": {}, - "source": [ - "### C) Time Tracking via Data Export from Time Tracking Tools" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "3597bf54", - "metadata": {}, - "outputs": [], - "source": [ - "if time_tracking_preference.value == \"Spreadsheet\":\n", - " timetracking_data = tuttle.timetracking.import_from_spreadsheet(\n", - " path=\"../../tuttle_tests/data/test_time_tracking_toggl.csv\",\n", - " preset=tuttle.timetracking.TimetrackingSpreadsheetPreset.Toggl,\n", - " )" - ] - }, - { - "cell_type": "markdown", - "id": "b1b31124", - "metadata": {}, - "source": [ - "Check if the format of your spreadsheets is already supoorted as a preset. Otherwise, set the arguments of `import_from_spreadsheet` to match your required format." - ] - }, - { - "cell_type": "markdown", - "id": "b0ebb379", - "metadata": {}, - "source": [ - "After a successful import, you can pass the resulting `timetracking_data` wherever as a `source` to the following functions." - ] - }, - { - "cell_type": "markdown", - "id": "9255660e", - "metadata": {}, - "source": [ - "## Generating Time Sheets" - ] - }, - { - "cell_type": "markdown", - "id": "43819042", - "metadata": {}, - "source": [ - "Now that we have connected a source for time tracking data, we can pass it to other functions. In the following we generate **time sheets**. \n", - "\n", - "A **time sheet** \n", - "- can be rendered to a document for reporting purposes\n", - "- is the basis for an **invoice** - more on invoicing later." - ] - }, - { - "cell_type": "markdown", - "id": "aaf27374", - "metadata": {}, - "source": [ - "**Select a project**" - ] - }, - { - "cell_type": "markdown", - "id": "4b8ed12d", - "metadata": {}, - "source": [ - "**Example**" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "d88dbf28", - "metadata": {}, - "outputs": [], - "source": [ - "my_project = controller.get_project(title=\"Heating Repair\")" - ] - }, - { - "cell_type": "markdown", - "id": "0de4110d", - "metadata": {}, - "source": [ - "_Your turn: Select one of your projects by title_" - ] - }, - { - "cell_type": "markdown", - "id": "aaa10a30", - "metadata": {}, - "source": [ - "**Generate a time sheet**" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "e37fa0b6", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/cls/Documents/Work/Projects/PrototypeFund/Dev/tuttle/tuttle/timetracking.py:56: FutureWarning: Value based partial slicing on non-monotonic DatetimeIndexes with non-existing keys is deprecated and will raise a KeyError in a future Version.\n", - " timetracking_data.loc[period_start:period_end].query(tag_query).sort_index()\n", - "/Users/cls/miniforge3/envs/tuttle/lib/python3.10/site-packages/sqlmodel/orm/session.py:101: SAWarning: Dialect sqlite+pysqlite does *not* support Decimal objects natively, and SQLAlchemy must convert from floating point - rounding errors and other issues may occur. Please consider storing Decimal numbers as strings or integers on this platform for lossless storage.\n", - " return super().execute( # type: ignore\n" - ] - } - ], - "source": [ - "my_timesheet = tuttle.timetracking.generate_timesheet(\n", - " source=my_calendar,\n", - " project=my_project,\n", - " period_start=\"February 2022\",\n", - " period_end=\"March 2022\",\n", - " item_description=my_project.title,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "bb69a222", - "metadata": {}, - "source": [ - "_Your turn: Generate a timesheet for your project_" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a1d27578", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "id": "850436db", - "metadata": {}, - "source": [ - "**Display the timesheet data**" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "1bca4e59", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
idtimesheet_idbeginenddurationtitletagdescription
0NoneNone2022-02-17 01:00:00+01:002022-02-18 01:00:00+01:000 days 08:00:00#HeatingRepair#HeatingRepairHeating Repair
1NoneNone2022-02-18 01:00:00+01:002022-02-19 01:00:00+01:000 days 08:00:00#HeatingRepair#HeatingRepairHeating Repair
\n", - "
" - ], - "text/plain": [ - " id timesheet_id begin end \\\n", - "0 None None 2022-02-17 01:00:00+01:00 2022-02-18 01:00:00+01:00 \n", - "1 None None 2022-02-18 01:00:00+01:00 2022-02-19 01:00:00+01:00 \n", - "\n", - " duration title tag description \n", - "0 0 days 08:00:00 #HeatingRepair #HeatingRepair Heating Repair \n", - "1 0 days 08:00:00 #HeatingRepair #HeatingRepair Heating Repair " - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "my_timesheet.table" - ] - }, - { - "cell_type": "markdown", - "id": "b83dfdc8", - "metadata": {}, - "source": [ - "**Render the timesheet document**" - ] - }, - { - "cell_type": "markdown", - "id": "989768ed", - "metadata": {}, - "source": [ - "The following function renders a timesheet to an HTML-based layout:" - ] - }, - { - "cell_type": "markdown", - "id": "301ecedb", - "metadata": {}, - "source": [ - "You can display the HTML in the notebook..." - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "f0ea30df", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2022-11-01 16:32:21.447 | INFO | tuttle.rendering:get_template_path:23 - Template path: /Users/cls/Documents/Work/Projects/PrototypeFund/Dev/tuttle/templates/timesheet-anvil\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " Timehseet Heating Repair - February 2022 - March 2022\n", - " \n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "\n", - "\n", - "\n", - "
\n", - "\n", - "
\n", - " \n", - " \n", - "
\n", - "\n", - " \n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
\n", - "
\n", - " \n", - "Main Street 9999
\n", - "55555 Sao Paolo
\n", - "Brazil\n", - "\n", - "
\n", - " Harry Tuttle
\n", - " \n", - "Main Street 450
\n", - "555555 Sao Paolo
\n", - "Brazil\n", - "\n", - "
\n", - "\n", - "
\n", - "\n", - " \n", - "\n", - "
\n", - " Timesheet: Heating Repair - February 2022 - March 2022\n", - " \n", - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
BeginEndDurationDescription
2022-02-17 01:00:00+01:002022-02-18 01:00:00+01:000 days 08:00:00Heating Repair
2022-02-18 01:00:00+01:002022-02-19 01:00:00+01:000 days 08:00:00Heating Repair
\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
Total16.0 hours
\n", - "\n", - "\n", - "\n", - "
\n", - "
\n", - "
\n", - "
\n", - "\n", - "
\n", - "\n", - "\n", - "\n", - "" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "display.HTML(\n", - " tuttle.rendering.render_timesheet(\n", - " user=controller.user,\n", - " timesheet=my_timesheet,\n", - " )\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "ead51bea", - "metadata": { - "tags": [] - }, - "source": [ - "... or render it to a file:" - ] - }, - { - "cell_type": "markdown", - "id": "830e9ab4", - "metadata": { - "tags": [] - }, - "source": [ - "_Set the path to a folder where you want your invoices to appear_:" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "35670e32", - "metadata": {}, - "outputs": [], - "source": [ - "timesheet_dir = Path.home() / \"Downloads\"" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "id": "a278cca7", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2022-11-01 16:32:21.465 | INFO | tuttle.rendering:get_template_path:23 - Template path: /Users/cls/Documents/Work/Projects/PrototypeFund/Dev/tuttle/templates/timesheet-anvil\n" - ] - } - ], - "source": [ - "tuttle.rendering.render_timesheet(\n", - " user=controller.user,\n", - " timesheet=my_timesheet,\n", - " style=\"anvil\",\n", - " out_dir=timesheet_dir,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "0a3aede9", - "metadata": {}, - "source": [ - "This will create a folder named with the timesheet title, containing the timesheet as an HTML document." - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "id": "429d8769", - "metadata": {}, - "outputs": [], - "source": [ - "timesheet_path = str(timesheet_dir / f\"Invoice-{my_timesheet.title}\" / f\"Timesheet-{my_timesheet.title}.html\")" - ] - }, - { - "cell_type": "markdown", - "id": "dc184228", - "metadata": {}, - "source": [ - "You can also render the timesheet to PDF. For now, this requires the native [wkhtmltopdf](https://wkhtmltopdf.org) tool to be installed." - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "id": "d6e662fe", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2022-11-01 16:32:21.483 | INFO | tuttle.rendering:get_template_path:23 - Template path: /Users/cls/Documents/Work/Projects/PrototypeFund/Dev/tuttle/templates/timesheet-anvil\n", - "2022-11-01 16:32:21.491 | INFO | tuttle.rendering:convert_html_to_pdf:38 - converting html to pdf: /Users/cls/Downloads/Timesheet-Heating Repair - February 2022 - March 2022/Timesheet-Heating Repair - February 2022 - March 2022.html -> /Users/cls/Downloads/Timesheet-Heating Repair - February 2022 - March 2022/Timesheet-Heating Repair - February 2022 - March 2022.pdf\n", - "2022-11-01 16:32:25.215 | DEBUG | tuttle.rendering:_convert_html_to_pdf_with_weasyprint:80 - css_paths: [PosixPath('/Users/cls/Downloads/Timesheet-Heating Repair - February 2022 - March 2022/timesheet.css'), PosixPath('/Users/cls/Downloads/Timesheet-Heating Repair - February 2022 - March 2022/web/web-base.css'), PosixPath('/Users/cls/Downloads/Timesheet-Heating Repair - February 2022 - March 2022/web/modern-normalize.css')]\n", - "1 extra bytes in post.stringData array\n", - "'created' timestamp seems very low; regarding as unix timestamp\n", - "1 extra bytes in post.stringData array\n" - ] - } - ], - "source": [ - "tuttle.rendering.render_timesheet(\n", - " user=controller.user,\n", - " timesheet=my_timesheet,\n", - " style=\"anvil\",\n", - " out_dir=timesheet_dir,\n", - " document_format=\"pdf\",\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "d77571a2", - "metadata": { - "tags": [] - }, - "source": [ - "## Invoicing" - ] - }, - { - "cell_type": "markdown", - "id": "54990dd9", - "metadata": {}, - "source": [ - "Now that we have set up our user info, clients, contracts and projects, as well as a source for time tracking data, we are ready to automatically generate invoices." - ] - }, - { - "cell_type": "markdown", - "id": "f2f63019", - "metadata": {}, - "source": [ - "### Workflow" - ] - }, - { - "cell_type": "markdown", - "id": "4cdbe7db", - "metadata": {}, - "source": [ - "_1. Select a project_" - ] - }, - { - "cell_type": "markdown", - "id": "ab994b0b", - "metadata": {}, - "source": [ - "**Example**" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "id": "dacaa031", - "metadata": {}, - "outputs": [], - "source": [ - "my_project = controller.get_project(title=\"Heating Repair\")" - ] - }, - { - "cell_type": "markdown", - "id": "c9a08a8d", - "metadata": {}, - "source": [ - "2. Generate an invoice for one or more timesheet(s)." - ] - }, - { - "cell_type": "markdown", - "id": "455de131", - "metadata": {}, - "source": [ - "**Example**" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "id": "b55b6251", - "metadata": {}, - "outputs": [], - "source": [ - "my_invoice = tuttle.invoicing.generate_invoice(\n", - " timesheets=[\n", - " my_timesheet,\n", - " ],\n", - " contract=my_project.contract,\n", - " date=datetime.date.today(),\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "id": "108de4d0", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'2022-11-01-01'" - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "my_invoice.number" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "id": "5dc591a0", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Decimal('952.000000000000')" - ] - }, - "execution_count": 24, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "my_invoice.total" - ] - }, - { - "cell_type": "markdown", - "id": "bfe582b9", - "metadata": {}, - "source": [ - "5. Render the invoice to a document template:" - ] - }, - { - "cell_type": "markdown", - "id": "3c83f9ce", - "metadata": {}, - "source": [ - "You can display the HTML in the notebook..." - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "id": "eb3b9b55", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2022-11-01 16:32:25.529 | INFO | tuttle.rendering:get_template_path:23 - Template path: /Users/cls/Documents/Work/Projects/PrototypeFund/Dev/tuttle/templates/invoice-anvil\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " Invoice No. 2022-11-01-01\n", - " \n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "\n", - "
\n", - "\n", - "
\n", - " \n", - " \n", - "
\n", - "\n", - " \n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
\n", - "
\n", - " \n", - "Main Street 9999
\n", - "55555 Sao Paolo
\n", - "Brazil\n", - "\n", - "
\n", - " Harry Tuttle
\n", - " Heating Engineer
\n", - "
\n", - " \n", - "Main Street 450
\n", - "555555 Sao Paolo
\n", - "Brazil\n", - "\n", - "
\n", - "
\n", - " mail@tuttle.com
\n", - " +55555555555
\n", - " https://tuttle-dev.github.io/tuttle/\n", - "
\n", - "\n", - "
\n", - "\n", - " \n", - "\n", - "
\n", - "
\n", - " Invoice Date: 2022-11-01
\n", - " Invoice Number: 2022-11-01-01\n", - "
\n", - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
DateDescriptionQtyUnitUnit PriceVAT%Subtotal
2022-02-17 - 2022-02-19Heating Repair - February 2022 - March 202216hour€50.00 €19.0 %€800.00
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
Payment InfoDue ByTotal VATTotal Due
\n", - "
\n", - " IBAN: BZ99830994950003161565
\n", - "
\n", - "
2022-11-15€152.00€952.00
\n", - "\n", - "
\n", - "

\n", - "
\n", - " Thank you for your business\n", - "

\n", - "
\n", - "
\n", - "
\n", - " Harry Tuttle\n", - "
\n", - "\n", - "
\n", - "
\n", - " IBAN: BZ99830994950003161565  |\n", - " BIC: ABCDEFGH |\n", - " VAT No: DZ-015\n", - "
\n", - "
\n", - "\n", - "\n", - "
\n", - "\n", - "\n", - "\n", - "" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 25, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "display.HTML(\n", - " tuttle.rendering.render_invoice(\n", - " user=controller.user, \n", - " invoice=my_invoice,\n", - " style=None\n", - " )\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "4f855a52", - "metadata": { - "tags": [] - }, - "source": [ - "... or render it to a file:" - ] - }, - { - "cell_type": "markdown", - "id": "7f95058d", - "metadata": { - "tags": [] - }, - "source": [ - "_Set the path to a folder where you want your invoices to appear_:" - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "id": "b7158baa", - "metadata": {}, - "outputs": [], - "source": [ - "invoice_dir = Path.home() / \"Downloads\"" - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "id": "603a0624", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2022-11-01 16:32:25.560 | INFO | tuttle.rendering:get_template_path:23 - Template path: /Users/cls/Documents/Work/Projects/PrototypeFund/Dev/tuttle/templates/invoice-anvil\n" - ] - } - ], - "source": [ - "tuttle.rendering.render_invoice(\n", - " user=controller.user, \n", - " invoice=my_invoice,\n", - " style=\"anvil\",\n", - " out_dir=invoice_dir,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "bf13d21e", - "metadata": {}, - "source": [ - "This will create a folder named with the inovice number, containing the invoice as an HTML document." - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "id": "aab80fd8", - "metadata": {}, - "outputs": [], - "source": [ - "invoice_path = str(invoice_dir / f\"Invoice-{my_invoice.number}\" / f\"Invoice-{my_invoice.number}.html\")" - ] - }, - { - "cell_type": "markdown", - "id": "02bc40f6", - "metadata": {}, - "source": [ - "_Your turn:_" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "34c8ce5d", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "id": "1a6bd14d", - "metadata": {}, - "source": [ - "You can also render the invoice to PDF. For now, this requires the native [wkhtmltopdf](https://wkhtmltopdf.org) tool to be installed." - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "id": "13dd79d0", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2022-11-01 16:32:25.580 | INFO | tuttle.rendering:get_template_path:23 - Template path: /Users/cls/Documents/Work/Projects/PrototypeFund/Dev/tuttle/templates/invoice-anvil\n", - "2022-11-01 16:32:25.589 | INFO | tuttle.rendering:convert_html_to_pdf:38 - converting html to pdf: /Users/cls/Downloads/2022-11-01-01-sam/2022-11-01-01-sam.html -> /Users/cls/Downloads/2022-11-01-01-sam/2022-11-01-01-sam.pdf\n", - "2022-11-01 16:32:25.590 | DEBUG | tuttle.rendering:_convert_html_to_pdf_with_weasyprint:80 - css_paths: [PosixPath('/Users/cls/Downloads/2022-11-01-01-sam/invoice.css'), PosixPath('/Users/cls/Downloads/2022-11-01-01-sam/web/web-base.css'), PosixPath('/Users/cls/Downloads/2022-11-01-01-sam/web/modern-normalize.css')]\n", - "1 extra bytes in post.stringData array\n", - "'created' timestamp seems very low; regarding as unix timestamp\n", - "1 extra bytes in post.stringData array\n" - ] - } - ], - "source": [ - "tuttle.rendering.render_invoice(\n", - " user=controller.user, \n", - " invoice=my_invoice,\n", - " style=\"anvil\",\n", - " out_dir=invoice_dir,\n", - " document_format=\"pdf\"\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "fb588650", - "metadata": {}, - "source": [ - "### Send the Invoice Automatically" - ] - }, - { - "cell_type": "markdown", - "id": "e3bdc577", - "metadata": {}, - "source": [ - "![](img/underconstruction.jpg)" - ] - }, - { - "cell_type": "markdown", - "id": "82d7a96a", - "metadata": {}, - "source": [ - "------" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "tuttle", - "language": "python", - "name": "ex" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.6" - }, - "vscode": { - "interpreter": { - "hash": "5727a89f8f4826e6ce3fb9da18a08727b274ba76880924157270e222bba4a1ac" - } - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/walkthrough/04-planning-forecasting.ipynb b/notebooks/walkthrough/04-planning-forecasting.ipynb deleted file mode 100644 index 3f6d7f9f..00000000 --- a/notebooks/walkthrough/04-planning-forecasting.ipynb +++ /dev/null @@ -1,530 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "8f997ef6", - "metadata": {}, - "source": [ - "# Planning Ahead and Forecasting Revenue\n" - ] - }, - { - "cell_type": "markdown", - "id": "a1cbd864", - "metadata": {}, - "source": [ - "Planning your time is essential for a freelance business. Tuttle enables you to easily forecast your revenue based on the planning decisions that you make." - ] - }, - { - "cell_type": "markdown", - "id": "1e013f44", - "metadata": {}, - "source": [ - "## Preamble" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "c346600a", - "metadata": {}, - "outputs": [], - "source": [ - "from pathlib import Path\n", - "import ipywidgets\n", - "from IPython import display\n", - "import datetime" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "fdf9c1f0", - "metadata": {}, - "outputs": [], - "source": [ - "import tuttle" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "3de4a2db", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2022-11-01 16:33:16.849 | INFO | tuttle.controller:create_model:76 - creating database model\n" - ] - } - ], - "source": [ - "controller = tuttle.controller.Controller(\n", - " preferences=tuttle.preferences.Preferences(\n", - " invoice_dir=Path(\"Invoices\"),\n", - " ),\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "d89dffe4", - "metadata": {}, - "source": [ - "## How to Plan Project Time" - ] - }, - { - "cell_type": "markdown", - "id": "055e240e", - "metadata": {}, - "source": [ - "In the following we assume that you plan your time in a digital calendar. Planning ahead works just like time tracking: In a dedicated calendar, enter future blocks of time and tag them with a unique project tag for your projects. " - ] - }, - { - "cell_type": "markdown", - "id": "a8cdf2c8", - "metadata": {}, - "source": [ - "Currently we support these methods of entering your time planning calendar data:\n", - " \n", - "1. **Cloud Calendar**: Querying your cloud calendar: Log into your cloud calendar provider and import your calendar events seamlessly.\n", - "2. **File Calendar**: Export your calendar in the iCalendar format and parse it." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "103ef73b", - "metadata": {}, - "outputs": [], - "source": [ - "time_planning_preference = ipywidgets.RadioButtons(options=[\"File Calendar\", \"Cloud Calendar\"])" - ] - }, - { - "cell_type": "markdown", - "id": "a0522eee", - "metadata": {}, - "source": [ - "_Select your prereference for importing time tracking data:_" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "469f60e4", - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "d844863c0f08462f9c69a7ca6da00d4b", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "RadioButtons(options=('File Calendar', 'Cloud Calendar'), value='File Calendar')" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "time_planning_preference" - ] - }, - { - "cell_type": "markdown", - "id": "61f40fe5", - "metadata": { - "tags": [] - }, - "source": [ - "### A) Time Planning via Cloud Calendar" - ] - }, - { - "cell_type": "markdown", - "id": "86539c8f", - "metadata": {}, - "source": [ - "If you have an Apple iCloud account, follow this process. (We are working on connectors with other cloud services)" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "2ee30b13", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'mail@tuttle.com'" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "icloud_username = controller.user.icloud_account.user_name\n", - "icloud_username" - ] - }, - { - "cell_type": "markdown", - "id": "d5a72000", - "metadata": {}, - "source": [ - "Connect to your cloud calendar that contains your time tracking events:" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "49e768f7", - "metadata": {}, - "outputs": [], - "source": [ - "from tuttle.calendar import ICloudCalendar" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "df4ae8f0", - "metadata": {}, - "outputs": [], - "source": [ - "if time_planning_preference.value == \"Cloud Calendar\":\n", - " my_planning_calendar = ICloudCalendar(\n", - " icloud=tuttle.cloud.login_iCloud(\n", - " user_name=icloud_username,\n", - " ),\n", - " name=input(\"calendar name: \"),\n", - " )" - ] - }, - { - "cell_type": "markdown", - "id": "b1d56297", - "metadata": {}, - "source": [ - "### B) Time Tracking via Calendar Import" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "3cfa810a", - "metadata": {}, - "outputs": [], - "source": [ - "from tuttle.calendar import FileCalendar" - ] - }, - { - "cell_type": "markdown", - "id": "1fb19a7a", - "metadata": {}, - "source": [ - "**Example: file calendar**" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "84a06e0d", - "metadata": {}, - "outputs": [], - "source": [ - "planning_calendar_path = Path(\"../../tuttle_tests/data/TuttleDemo-TimePlanning.ics\")" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "2e766480", - "metadata": {}, - "outputs": [], - "source": [ - "if time_planning_preference.value == \"File Calendar\":\n", - " my_planning_calendar = FileCalendar(\n", - " path=planning_calendar_path, \n", - " name=\"TimeTracking\"\n", - " )" - ] - }, - { - "cell_type": "markdown", - "id": "44940432", - "metadata": {}, - "source": [ - "## Forecasting Revenue based on Time Planning" - ] - }, - { - "cell_type": "markdown", - "id": "c42326e7", - "metadata": {}, - "source": [ - "![](img/underconstruction.jpg)" - ] - }, - { - "cell_type": "markdown", - "id": "30e2d8c0", - "metadata": {}, - "source": [ - "Now that we have connected a source for time tracking data, we can pass it to other functions. In the following we want to display our expected future revenue based on our planning." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "c0ccd7c0", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/cls/Documents/Work/Projects/PrototypeFund/Dev/tuttle/tuttle/timetracking.py:262: FutureWarning: Value based partial slicing on non-monotonic DatetimeIndexes with non-existing keys is deprecated and will raise a KeyError in a future Version.\n", - " planning_data = planning_data[str(from_date) :]\n", - "/Users/cls/Documents/Work/Projects/PrototypeFund/Dev/tuttle/tuttle/controller.py:204: FutureWarning: The default value of numeric_only in DataFrameGroupBy.sum is deprecated. In a future version, numeric_only will default to False. Either specify numeric_only or select only columns which should be valid for the function.\n", - " planning_data.filter([\"tag\", \"duration\"]).groupby(\"tag\").sum()\n", - "/Users/cls/miniforge3/envs/tuttle/lib/python3.10/site-packages/altair/utils/core.py:317: FutureWarning: iteritems is deprecated and will be removed in a future version. Use .items instead.\n", - " for col_name, dtype in df.dtypes.iteritems():\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "
\n", - "" - ], - "text/plain": [ - "alt.Chart(...)" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "controller.eval_time_planning(\n", - " my_planning_calendar,\n", - " by=\"project\",\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "c866bd5e", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/cls/Documents/Work/Projects/PrototypeFund/Dev/tuttle/tuttle/timetracking.py:262: FutureWarning: Value based partial slicing on non-monotonic DatetimeIndexes with non-existing keys is deprecated and will raise a KeyError in a future Version.\n", - " planning_data = planning_data[str(from_date) :]\n", - "/Users/cls/Documents/Work/Projects/PrototypeFund/Dev/tuttle/tuttle/controller.py:210: FutureWarning: The default value of numeric_only in DataFrameGroupBy.sum is deprecated. In a future version, numeric_only will default to False. Either specify numeric_only or select only columns which should be valid for the function.\n", - " .sum()\n", - "/Users/cls/miniforge3/envs/tuttle/lib/python3.10/site-packages/altair/utils/core.py:317: FutureWarning: iteritems is deprecated and will be removed in a future version. Use .items instead.\n", - " for col_name, dtype in df.dtypes.iteritems():\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "
\n", - "" - ], - "text/plain": [ - "alt.Chart(...)" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "controller.eval_time_planning(\n", - " my_planning_calendar,\n", - " by=(\"month\", \"project\"),\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "7d9ef0a6", - "metadata": {}, - "source": [ - "_Your turn: Plan your time and forecast your revenue._" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f267d8be", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "id": "91d26c9f", - "metadata": {}, - "source": [ - "_Feedback question: Would you like to see other forms of representing your revenue forecast? Which ones would be useful?_" - ] - }, - { - "cell_type": "markdown", - "id": "d33909fe", - "metadata": {}, - "source": [ - "--------" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "tuttle", - "language": "python", - "name": "ex" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.6" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/walkthrough/img/underconstruction.jpg b/notebooks/walkthrough/img/underconstruction.jpg deleted file mode 100644 index ae824d29..00000000 Binary files a/notebooks/walkthrough/img/underconstruction.jpg and /dev/null differ