Skip to content

Commit a3d5549

Browse files
committed
checkpoint: invoice rendering
1 parent 502b211 commit a3d5549

File tree

2 files changed

+38
-20
lines changed

2 files changed

+38
-20
lines changed

tuttle/model.py

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
"""Object model."""
22

3-
import email
43
from typing import Optional, List, Dict, Type
54
from pydantic import constr, BaseModel, condecimal
65
from enum import Enum
76
import datetime
8-
import hashlib
9-
import uuid
107
import textwrap
118

129
import sqlalchemy
@@ -50,9 +47,10 @@ def to_dataframe(items: List[Type[BaseModel]]) -> pandas.DataFrame:
5047

5148

5249
def OneToOneRelationship(back_populates):
50+
"""Define a relationship as one-to-one."""
5351
return Relationship(
5452
back_populates=back_populates,
55-
sa_relationship_kwargs={"uselist": False},
53+
sa_relationship_kwargs={"uselist": False, "lazy": "subquery"},
5654
)
5755

5856

@@ -150,6 +148,11 @@ class User(SQLModel, table=True):
150148
)
151149
# TODO: path to logo image
152150
logo: Optional[str]
151+
# User 1:n Invoices
152+
# invoices: List["Invoice"] = Relationship(
153+
# back_populates="user",
154+
# sa_relationship_kwargs={"lazy": "subquery"},
155+
# )
153156

154157
@property
155158
def bank_account_not_set(self) -> bool:
@@ -454,10 +457,7 @@ class Timesheet(SQLModel, table=True):
454457
id: Optional[int] = Field(default=None, primary_key=True)
455458
title: str
456459
date: datetime.date = Field(description="The date of creation of the timesheet")
457-
# period: str
458-
# table: pandas.DataFrame
459-
# TODO: store dataframe as dict
460-
# table: Dict = Field(default={}, sa_column=sqlalchemy.Column(sqlalchemy.JSON))
460+
461461
# Timesheet n:1 Project
462462
project_id: Optional[int] = Field(default=None, foreign_key="project.id")
463463
project: Project = Relationship(
@@ -469,6 +469,18 @@ class Timesheet(SQLModel, table=True):
469469
comment: Optional[str] = Field(description="A comment on the timesheet.")
470470
items: List[TimeTrackingItem] = Relationship(back_populates="timesheet")
471471

472+
rendered: bool = Field(
473+
default=False,
474+
description="Whether the Timesheet has been rendered as a PDF.",
475+
)
476+
477+
# Timesheet 1:1 Invoice
478+
# FIXME: Could not determine join condition between parent/child tables
479+
# invoice_id: Optional[int] = Field(default=None, foreign_key="invoice.id")
480+
# invoice: Optional["Invoice"] = OneToOneRelationship(
481+
# back_populates="timesheet",
482+
# )
483+
472484
# class Config:
473485
# arbitrary_types_allowed = True
474486

@@ -492,16 +504,18 @@ class Invoice(SQLModel, table=True):
492504
"""An invoice is a bill for a client."""
493505

494506
id: Optional[int] = Field(default=None, primary_key=True)
495-
number: str
507+
number: Optional[str] = Field(description="The invoice number. Auto-generated.")
496508
# date and time
497509
date: datetime.date = Field(
498510
description="The date of the invoice",
499511
)
500-
# due_date: datetime.date
501-
# sent_date: datetime.date
502-
# Invoice 1:n Timesheet ?
512+
513+
# TODO: sent_date: datetime.datetime = Field(description="The date the invoice was sent.")
514+
# Invoice 1:1 Timesheet
503515
# timesheet_id: Optional[int] = Field(default=None, foreign_key="timesheet.id")
504-
# timesheet: Timesheet = Relationship(back_populates="invoice")
516+
# timesheet: Timesheet = OneToOneRelationship(
517+
# back_populates="invoice",
518+
# )
505519
# Invoice n:1 Contract ?
506520
contract_id: Optional[int] = Field(default=None, foreign_key="contract.id")
507521
contract: Contract = Relationship(
@@ -529,7 +543,7 @@ class Invoice(SQLModel, table=True):
529543
)
530544
rendered: bool = Field(
531545
default=False,
532-
description="If the invoice has been rendered as a PDF.",
546+
description="Whether the invoice has been rendered as a PDF.",
533547
)
534548

535549
#
@@ -548,7 +562,7 @@ def total(self) -> Decimal:
548562
"""Total invoiced amount."""
549563
return self.sum + self.VAT_total
550564

551-
def generate_number(self, pattern=None, counter=None):
565+
def generate_number(self, pattern=None, counter=None) -> str:
552566
"""Generate an invoice number"""
553567
date_prefix = self.date.strftime("%Y-%m-%d")
554568
# suffix = hashlib.shake_256(str(uuid.uuid4()).encode("utf-8")).hexdigest(2)
@@ -559,9 +573,12 @@ def generate_number(self, pattern=None, counter=None):
559573
self.number = f"{date_prefix}-{suffix}"
560574

561575
@property
562-
def due_date(self):
576+
def due_date(self) -> Optional[datetime.date]:
563577
"""Date until which payment is due."""
564-
return self.date + datetime.timedelta(days=self.contract.term_of_payment)
578+
if self.contract.term_of_payment:
579+
return self.date + datetime.timedelta(days=self.contract.term_of_payment)
580+
else:
581+
return None
565582

566583
@property
567584
def client(self):

tuttle/rendering.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,8 @@ def emit_pdf(finished):
117117
def render_invoice(
118118
user: User,
119119
invoice: Invoice,
120+
out_dir,
120121
document_format: str = "pdf",
121-
out_dir: str = None,
122122
style: str = "anvil",
123123
only_final: bool = False,
124124
) -> str:
@@ -212,10 +212,11 @@ def as_percentage(number):
212212
def render_timesheet(
213213
user: User,
214214
timesheet: Timesheet,
215+
out_dir,
215216
document_format: str = "html",
216-
out_dir: str = None,
217217
style: str = "anvil",
218-
) -> str:
218+
only_final: bool = False,
219+
):
219220
"""Render a Timeseheet using an HTML template.
220221
221222
Args:

0 commit comments

Comments
 (0)