Skip to content

Commit

Permalink
V0.5.3.2 (#144)
Browse files Browse the repository at this point in the history
* ItemizeMixIn

* v0.5.3.2
ItemizeAPI

* ItemizeMixIn implementation for Bills, Invoices and Estimate Models.
Template bugfixes.

* ItemizeMixIn implementation for PurchaseOrderModel.

* ItemizeMixIn migrate operation.

* QuickStart Notebook update with ItemizeMixIn methods.

* QuickStart Notebook update with ItemizeMixIn methods.

* ItemizeMixIn Documentation.
  • Loading branch information
elarroba authored Jun 1, 2023
1 parent 3b2c856 commit 75c9bad
Show file tree
Hide file tree
Showing 19 changed files with 1,003 additions and 556 deletions.
694 changes: 276 additions & 418 deletions Pipfile.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion django_ledger/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
default_app_config = 'django_ledger.apps.DjangoLedgerConfig'

"""Django Ledger"""
__version__ = '0.5.3.1'
__version__ = '0.5.3.2'
__license__ = 'GPLv3 License'

__author__ = 'Miguel Sanda'
Expand Down
69 changes: 47 additions & 22 deletions django_ledger/models/bill.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@
from django.utils.translation import gettext_lazy as _

from django_ledger.models.entity import EntityModel
from django_ledger.models.items import ItemTransactionModelQuerySet, ItemTransactionModel
from django_ledger.models.mixins import CreateUpdateMixIn, AccrualMixIn, MarkdownNotesMixIn, PaymentTermsMixIn
from django_ledger.models.items import ItemTransactionModelQuerySet, ItemTransactionModel, ItemModel, ItemModelQuerySet
from django_ledger.models.mixins import (CreateUpdateMixIn, AccrualMixIn, MarkdownNotesMixIn,
PaymentTermsMixIn, ItemizeMixIn)
from django_ledger.models.utils import lazy_loader
from django_ledger.settings import (DJANGO_LEDGER_DOCUMENT_NUMBER_PADDING, DJANGO_LEDGER_BILL_NUMBER_PREFIX)

Expand Down Expand Up @@ -242,6 +243,7 @@ def for_entity(self, entity_slug, user_model) -> BillModelQuerySet:


class BillModelAbstract(AccrualMixIn,
ItemizeMixIn,
PaymentTermsMixIn,
MarkdownNotesMixIn,
CreateUpdateMixIn):
Expand Down Expand Up @@ -488,19 +490,29 @@ def configure(self,

return self.ledger, self

# State..
def get_migrate_state_desc(self) -> str:
"""
Description used when migrating transactions into the LedgerModel.
# ### ItemizeMixIn implementation START...
def can_migrate_itemtxs(self) -> bool:
return self.is_draft()

Returns
_______
str
Description as a string.
"""
return f'Bill {self.bill_number} account adjustment.'
def migrate_itemtxs(self, itemtxs: Dict, operation: str, commit: bool = False):
itemtxs_batch = super().migrate_itemtxs(itemtxs=itemtxs, commit=commit, operation=operation)
self.update_amount_due(itemtxs_qs=itemtxs_batch)
self.get_state(commit=True)

if commit:
self.save(update_fields=['amount_due',
'amount_receivable',
'amount_unearned',
'amount_earned',
'updated'])
return itemtxs_batch

def get_item_model_qs(self) -> ItemModelQuerySet:
return ItemModel.objects.filter(
entity_id__exact=self.ledger.entity_id
).bills()

def validate_item_transaction_qs(self, queryset: Union[ItemTransactionModelQuerySet, List[ItemTransactionModel]]):
def validate_itemtxs_qs(self, queryset: Union[ItemTransactionModelQuerySet, List[ItemTransactionModel]]):
"""
Validates that the entire ItemTransactionModelQuerySet is bound to the BillModel.
Expand All @@ -518,7 +530,7 @@ def validate_item_transaction_qs(self, queryset: Union[ItemTransactionModelQuery
def get_itemtxs_data(self,
queryset: Optional[ItemTransactionModelQuerySet] = None,
aggregate_on_db: bool = False,
) -> Tuple[ItemTransactionModelQuerySet, Dict]:
lazy_agg: bool = False) -> Tuple[ItemTransactionModelQuerySet, Dict]:
"""
Fetches the BillModel Items and aggregates the QuerySet.
Expand All @@ -539,7 +551,7 @@ def get_itemtxs_data(self,
'po_model',
'bill_model')
else:
self.validate_item_transaction_qs(queryset)
self.validate_itemtxs_qs(queryset)

if aggregate_on_db and isinstance(queryset, ItemTransactionModelQuerySet):
return queryset, queryset.aggregate(
Expand All @@ -549,7 +561,21 @@ def get_itemtxs_data(self,
return queryset, {
'total_amount__sum': sum(i.total_amount for i in queryset),
'total_items': len(queryset)
}
} if not lazy_agg else None

# ### ItemizeMixIn implementation END...

# State..
def get_migrate_state_desc(self) -> str:
"""
Description used when migrating transactions into the LedgerModel.
Returns
_______
str
Description as a string.
"""
return f'Bill {self.bill_number} account adjustment.'

def get_migration_data(self,
queryset: Optional[ItemTransactionModelQuerySet] = None) -> ItemTransactionModelQuerySet:
Expand All @@ -565,7 +591,7 @@ def get_migration_data(self,
if not queryset:
queryset = self.itemtransactionmodel_set.all()
else:
self.validate_item_transaction_qs(queryset)
self.validate_itemtxs_qs(queryset)

return queryset.order_by('item_model__expense_account__uuid',
'entity_unit__uuid',
Expand Down Expand Up @@ -601,7 +627,6 @@ def update_amount_due(self,
self.amount_due = round(itemtxs_agg['total_amount__sum'], 2)
return itemtxs_qs

# State
def is_draft(self) -> bool:
"""
Checks if the BillModel is in Draft status.
Expand Down Expand Up @@ -1014,7 +1039,7 @@ def mark_as_review(self,
if not itemtxs_qs:
itemtxs_qs = self.itemtransactionmodel_set.all()
else:
self.validate_item_transaction_qs(queryset=itemtxs_qs)
self.validate_itemtxs_qs(queryset=itemtxs_qs)

if not itemtxs_qs.count():
raise BillModelValidationError(message=f'Cannot review a {self.__class__.__name__} without items...')
Expand Down Expand Up @@ -1117,7 +1142,7 @@ def mark_as_approved(self,
)
self.bill_status = self.BILL_STATUS_APPROVED
self.date_approved = localdate() if not date_approved else date_approved
self.new_state(commit=True)
self.get_state(commit=True)
self.clean()
if commit:
self.save(update_fields=[
Expand Down Expand Up @@ -1226,13 +1251,13 @@ def mark_as_paid(self,
f'Cannot pay {self.__class__.__name__} before approved date {self.date_approved}.')

self.bill_status = self.BILL_STATUS_PAID
self.new_state(commit=True)
self.get_state(commit=True)
self.clean()

if not itemtxs_qs:
itemtxs_qs = self.itemtransactionmodel_set.all()
else:
self.validate_item_transaction_qs(queryset=itemtxs_qs)
self.validate_itemtxs_qs(queryset=itemtxs_qs)

if commit:
self.save(update_fields=[
Expand Down
4 changes: 4 additions & 0 deletions django_ledger/models/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -1234,6 +1234,10 @@ def create_bill(self,

return bill_model

def get_items_for_bill(self) -> ItemModelQuerySet:
item_model_qs: ItemModelQuerySet = self.itemmodel_set.all()
return item_model_qs.select_related('uom', 'entity').bills()

# ### INVOICE MANAGEMENT ####
def get_invoices(self):
"""
Expand Down
83 changes: 65 additions & 18 deletions django_ledger/models/estimate.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from datetime import date
from decimal import Decimal
from string import ascii_uppercase, digits
from typing import Union, Optional, List
from typing import Union, Optional, List, Dict
from uuid import uuid4, UUID

from django.contrib.auth import get_user_model
Expand All @@ -33,8 +33,8 @@
from django_ledger.models import BillModelQuerySet, InvoiceModelQuerySet
from django_ledger.models.customer import CustomerModel
from django_ledger.models.entity import EntityModel, EntityStateModel
from django_ledger.models.items import ItemTransactionModelQuerySet, ItemTransactionModel
from django_ledger.models.mixins import CreateUpdateMixIn, MarkdownNotesMixIn
from django_ledger.models.items import ItemTransactionModelQuerySet, ItemTransactionModel, ItemModelQuerySet, ItemModel
from django_ledger.models.mixins import CreateUpdateMixIn, MarkdownNotesMixIn, ItemizeMixIn
from django_ledger.models.purchase_order import PurchaseOrderModelQuerySet
from django_ledger.settings import DJANGO_LEDGER_DOCUMENT_NUMBER_PADDING, DJANGO_LEDGER_ESTIMATE_NUMBER_PREFIX

Expand Down Expand Up @@ -102,6 +102,9 @@ def estimates(self):
"""
return self.not_approved()

def draft(self):
return self.filter(status__exact=EstimateModelAbstract.CONTRACT_STATUS_DRAFT)


class EstimateModelManager(models.Manager):
"""
Expand Down Expand Up @@ -147,7 +150,9 @@ def for_entity(self, entity_slug: Union[EntityModel, str], user_model):
)


class EstimateModelAbstract(CreateUpdateMixIn, MarkdownNotesMixIn):
class EstimateModelAbstract(CreateUpdateMixIn,
ItemizeMixIn,
MarkdownNotesMixIn):
"""
This is the main abstract class which the EstimateModel database will inherit from.
The EstimateModel inherits functionality from the following MixIns:
Expand Down Expand Up @@ -1000,31 +1005,73 @@ def get_mark_as_void_message(self):
def get_html_id(self):
return f'djl-customer-estimate-id-{self.uuid}'

# ItemThroughModels...
# ### ItemizeMixIn implementation START...
def can_migrate_itemtxs(self) -> bool:
return self.is_draft()

def migrate_itemtxs(self, itemtxs: Dict, operation: str, commit: bool = False):
itemtxs_batch = super().migrate_itemtxs(itemtxs=itemtxs, commit=commit, operation=operation)
self.update_state(itemtxs_qs=itemtxs_batch)
self.clean()

if commit:
self.save(update_fields=[
'revenue_estimate',
'labor_estimate',
'equipment_estimate',
'material_estimate',
'other_estimate',
'updated'
])

return itemtxs_batch

def get_item_model_qs(self) -> ItemModelQuerySet:
return ItemModel.objects.filter(
entity_id__exact=self.entity_id
).estimates()

def validate_itemtxs_qs(self, queryset: Union[ItemTransactionModelQuerySet, List[ItemTransactionModel]]):
"""
Validates that the entire ItemTransactionModelQuerySet is bound to the EstimateModel.
Parameters
----------
queryset: ItemTransactionModelQuerySet or list of ItemTransactionModel.
ItemTransactionModelQuerySet to validate.
"""
valid = all([
i.ce_model_id == self.uuid for i in queryset
])
if not valid:
raise EstimateModelValidationError(f'Invalid queryset. All items must be assigned to Bill {self.uuid}')

def get_itemtxs_data(self,
itemtxs_qs: Optional[Union[ItemTransactionModelQuerySet, List[ItemTransactionModel]]] = None
) -> ItemTransactionModelQuerySet:
# todo: this needs to return an aggregate for consistency...
queryset: Optional[Union[ItemTransactionModelQuerySet, List[ItemTransactionModel]]] = None,
aggregate_on_db: bool = False,
lazy_agg: bool = False):

"""
Returns all ItemTransactionModels associated with the EstimateModel and a total aggregate.
Parameters
----------
itemtxs_qs: ItemTransactionModelQuerySet
queryset: ItemTransactionModelQuerySet
ItemTransactionModelQuerySet to use. Avoids additional DB query if provided.
Validated if provided.
Returns
-------
ItemTransactionModelQuerySet
"""
if not itemtxs_qs:
itemtxs_qs = self.itemtransactionmodel_set.select_related('item_model').all()
if not queryset:
queryset = self.itemtransactionmodel_set.select_related('item_model').all()
else:
self.validate_item_transaction_qs(itemtxs_qs)

return itemtxs_qs
self.validate_item_transaction_qs(queryset)
# todo: this needs to return an aggregate for consistency...
return queryset, None

# ### ItemizeMixIn implementation END...
def get_itemtxs_annotation(self, itemtxs_qs: Optional[ItemTransactionModelQuerySet] = None):
"""
Gets an annotated ItemTransactionModelQuerySet with additional average unit cost & revenue.
Expand All @@ -1041,7 +1088,7 @@ def get_itemtxs_annotation(self, itemtxs_qs: Optional[ItemTransactionModelQueryS
tuple
The original ItemTransactionModelQuerySet and the annotated ItemTransactionModelQuerySet.
"""
itemtxs_qs = self.get_itemtxs_data(itemtxs_qs)
itemtxs_qs, _ = self.get_itemtxs_data(itemtxs_qs)
return itemtxs_qs, itemtxs_qs.values(
'item_model_id', 'item_model__name'
).annotate(
Expand Down Expand Up @@ -1071,7 +1118,7 @@ def update_revenue_estimate(self, itemtxs_qs: Optional[ItemTransactionModelQuery
commit: bool
If True, the new revenue estimate will be committed into the DB.
"""
itemtxs_qs = self.get_itemtxs_data(itemtxs_qs)
itemtxs_qs, _ = self.get_itemtxs_data(itemtxs_qs)
self.revenue_estimate = sum(i.ce_revenue_estimate for i in itemtxs_qs)

if commit:
Expand All @@ -1092,7 +1139,7 @@ def update_cost_estimate(self, itemtxs_qs: Optional[ItemTransactionModelQuerySet
commit: bool
If True, the new revenue estimate will be committed into the DB.
"""
itemtxs_qs = self.get_itemtxs_data(itemtxs_qs=itemtxs_qs)
itemtxs_qs, _ = self.get_itemtxs_data(queryset=itemtxs_qs)
estimates = {
'labor': sum(a.ce_cost_estimate for a in itemtxs_qs if a.item_model.is_labor()),
'material': sum(a.ce_cost_estimate for a in itemtxs_qs if a.item_model.is_material()),
Expand All @@ -1119,7 +1166,7 @@ def update_cost_estimate(self, itemtxs_qs: Optional[ItemTransactionModelQuerySet

def update_state(self,
itemtxs_qs: Optional[Union[ItemTransactionModelQuerySet, List[ItemTransactionModel]]] = None):
itemtxs_qs = self.get_itemtxs_data(itemtxs_qs=itemtxs_qs)
itemtxs_qs, _ = self.get_itemtxs_data(queryset=itemtxs_qs)
self.update_cost_estimate(itemtxs_qs)
self.update_revenue_estimate(itemtxs_qs)

Expand Down
Loading

0 comments on commit 75c9bad

Please sign in to comment.