Skip to content

Commit 5191791

Browse files
authored
Merge pull request #225 from tuttle-dev/dev
Dev -> main
2 parents 69cb102 + 6d8de3e commit 5191791

File tree

7 files changed

+65
-40
lines changed

7 files changed

+65
-40
lines changed

CONTRIBUTING.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,3 +146,40 @@ $ git push --tags
146146
```
147147

148148
Travis will then deploy to PyPI if tests pass.
149+
150+
151+
## Architecture Notes
152+
153+
**The View**
154+
155+
- builds Ui,
156+
- reacts to data changes (by updating the Ui)
157+
- listens for events and forwards them to the Intent
158+
159+
**The Intent**
160+
161+
- receives events
162+
- if some data is affected by the event, figure out which data source corresponds to that data
163+
- transforms the event data to the data format required by the data source
164+
- transform returned data source data to the data format required by the Ui
165+
- else, no data is affected by the event, handle the event (often using a util class).
166+
- an example of this is sending invoices by mail.
167+
168+
**The Model (a.k.a data layer)**
169+
170+
- defines the entity
171+
- define the entity source (file, remote API, local database, in-memory cache, etc)
172+
- if a relational database is used, define the entity's relationship to other entities
173+
- maintain the integrity of that relation (conflict strategies for insert operations are defined here, and integrity errors are thrown here, for example)
174+
- defines classes that manipulate this source (open, read, write, ....)
175+
176+
177+
As you go outer in layers (the outmost layer is the Ui, the innermost is the data layer), communication can occur down_ward across layers, and horizontally (for lack of a better word) BUT a layer cannot skip the layer directly below it. This is to say:
178+
179+
* Data sources can communicate with each other. Thus `ClientDatasource.delete_client` can call. `ContractDatasource.get_contract ` for example.
180+
181+
* Intents can communicate with each other, and with any data source. Thus `ClientIntent` can call `ContractIntent` or `ContractDatasource` for example.
182+
The Ui can communicate with any intent (though often the Ui is tied to only a single intent, and the intent can instead call the 'other' intent). But it cannot communicate with a data source -> this would violate the do not skip layers rule.
183+
An inner layer cannot have a dependency on the layer above it. Thus an intent cannot instantiate a Ui class, and a data source cannot instantiate an Intent class.
184+
185+
![](assets/images/mvi-concept.png)

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,17 @@ $ pytest
112112
$ python app/app.py
113113
```
114114

115+
## Installation
116+
117+
### macOS
118+
119+
### Linux
120+
121+
### Windows
122+
123+
1. Requires [GTK](https://www.gtk.org).
124+
125+
115126
## Build
116127

117128
To build an app bundle, run

assets/images/mvi-concept.png

90.6 KB
Loading

requirements.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ babel
99
loguru
1010
pandera
1111
weasyprint
12-
matplotlib
1312
faker
1413
PyPDF2
1514
flet

tuttle/model.py

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -619,24 +619,14 @@ def sum(self) -> Decimal:
619619
def VAT_total(self) -> Decimal:
620620
"""Sum of VAT over all invoice items."""
621621
s = sum(item.VAT for item in self.items)
622-
return Decimal(s)
622+
return Decimal(round(s, 2))
623623

624624
@property
625625
def total(self) -> Decimal:
626626
"""Total invoiced amount."""
627627
t = self.sum + self.VAT_total
628628
return Decimal(t)
629629

630-
def generate_number(self, pattern=None, counter=None) -> str:
631-
"""Generate an invoice number"""
632-
date_prefix = self.date.strftime("%Y-%m-%d")
633-
# suffix = hashlib.shake_256(str(uuid.uuid4()).encode("utf-8")).hexdigest(2)
634-
# TODO: auto-increment suffix for invoices generated on the same day
635-
if counter is None:
636-
counter = 1
637-
suffix = f"{counter:02}"
638-
self.number = f"{date_prefix}-{suffix}"
639-
640630
@property
641631
def due_date(self) -> Optional[datetime.date]:
642632
"""Date until which payment is due."""

tuttle_tests/test_invoice.py

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Tests for the invoice module."""
22

33
import datetime
4+
from decimal import Decimal
45
from pathlib import Path
56

67
from tuttle import invoicing, timetracking, rendering
@@ -12,36 +13,39 @@ def test_invoice():
1213
the_invoice = Invoice(
1314
number="27B-6",
1415
date=datetime.date.today(),
15-
due_date=datetime.date.today() + datetime.timedelta(days=14),
16-
sent_date=datetime.date.today(),
1716
sent=True,
18-
paid="foo",
17+
paid=False,
1918
cancelled=False,
2019
)
2120

2221
item_1 = InvoiceItem(
2322
invoice=the_invoice,
24-
date=datetime.date.today(),
23+
start_date=datetime.date.today(),
24+
end_date=datetime.date.today(),
2525
quantity=10,
2626
unit="hours",
27-
unit_price=50,
27+
unit_price=Decimal(50),
2828
description="work work",
29-
VAT_rate=0.20,
29+
VAT_rate=Decimal(0.20),
3030
)
3131

3232
item_2 = InvoiceItem(
3333
invoice=the_invoice,
34-
date=datetime.date.today(),
34+
start_date=datetime.date.today(),
35+
end_date=datetime.date.today(),
3536
quantity=10,
3637
unit="hours",
37-
unit_price=100,
38+
unit_price=Decimal(100),
3839
description="work work",
39-
VAT_rate=0.20,
40+
VAT_rate=Decimal(0.20),
4041
)
4142

42-
assert the_invoice.sum == 1500
43-
assert the_invoice.VAT_total == 300
44-
assert the_invoice.total == 1800
43+
assert item_1.invoice == the_invoice
44+
assert item_2.invoice == the_invoice
45+
46+
assert the_invoice.sum == Decimal(1500)
47+
assert the_invoice.VAT_total == Decimal(300)
48+
assert the_invoice.total == Decimal(1800)
4549

4650

4751
def test_generate_invoice(

tuttle_tests/test_model.py

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -64,22 +64,6 @@ def test_model_creation():
6464
pass
6565

6666

67-
def test_user():
68-
user = model.User(
69-
name="Archibald Tuttle",
70-
subtitle="Heating Engineer",
71-
72-
)
73-
74-
icloud_account = model.ICloudAccount(
75-
user_name=user.email,
76-
)
77-
78-
user.icloud_account = icloud_account
79-
80-
assert icloud_account.user.name == "Archibald Tuttle"
81-
82-
8367
class TestUser:
8468
"""Tests for the User model."""
8569

0 commit comments

Comments
 (0)