Skip to content

Commit

Permalink
feat(sage_invoice): Add Csv export and receipt opition
Browse files Browse the repository at this point in the history
  • Loading branch information
radinceorc committed Sep 2, 2024
1 parent 4013d53 commit e327dfd
Show file tree
Hide file tree
Showing 38 changed files with 16,849 additions and 92 deletions.
2 changes: 1 addition & 1 deletion sage_invoice/admin/actions/download_html.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def export_as_html(modeladmin, request, queryset):

for static_path, archive_path in static_files.items():
absolute_static_path = os.path.join(
settings.BASE_DIR, "media", "static", static_path
settings.BASE_DIR, "sage_invoice", "static", static_path
)
if os.path.exists(absolute_static_path):
with open(absolute_static_path, "rb") as static_file:
Expand Down
100 changes: 56 additions & 44 deletions sage_invoice/admin/invoice.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from django.contrib import admin
from django.utils.translation import gettext_lazy as _
from import_export.admin import ImportExportModelAdmin

from sage_invoice.admin.actions import export_as_html, show_invoice
from sage_invoice.models import Invoice, InvoiceColumn, InvoiceItem, InvoiceTotal
from sage_invoice.resource import InvoiceResource


class InvoiceItemInline(admin.TabularInline):
Expand All @@ -23,7 +25,8 @@ class InvoiceTotalInline(admin.TabularInline):


@admin.register(Invoice)
class InvoiceAdmin(admin.ModelAdmin):
class InvoiceAdmin(ImportExportModelAdmin, admin.ModelAdmin):
resource_class = InvoiceResource
admin_priority = 1
list_display = ("title", "invoice_date", "customer_name", "status")
search_fields = ("customer_name", "status", "customer_email")
Expand All @@ -34,49 +37,58 @@ class InvoiceAdmin(admin.ModelAdmin):
readonly_fields = ("slug",)
actions = [export_as_html, show_invoice]

fieldsets = (
(
_("Invoice Details"),
{
"fields": (
"title",
"slug",
"invoice_date",
"tracking_code",
"due_date",
"customer_name",
"customer_email",
"category",
),
"description": _(
"Basic details of the invoice including title, date, and customer information."
),
},
),
(
_("Status & Notes"),
{
"fields": ("status", "notes"),
"description": _(
"Current status of the invoice and any additional notes."
),
},
),
(
_("Design Elements"),
{
"fields": (
"logo",
"signature",
"stamp",
"template_choice",
),
"description": _(
"Design-related elements like logo, and template choice."
),
},
),
)
class Media:
js = ("assets/js/invoice_admin.js",)

def get_fieldsets(self, request, obj=None):
fieldsets = (
(
_("Invoice Details"),
{
"fields": (
"title",
"slug",
"invoice_date",
"tracking_code",
"due_date",
"customer_name",
"customer_email",
"category",
"receipt",
),
"description": _(
"Basic details of the invoice including title, date, and customer information."
),
},
),
(
_("Status & Notes"),
{
"fields": ("status", "notes"),
"description": _(
"Current status of the invoice and any additional notes."
),
},
),
)
fieldsets += (
(
_("Design Elements"),
{
"fields": (
"logo",
"signature",
"stamp",
"template_choice",
),
"description": _(
"Design-related elements like logo, and template choice."
),
},
),
)

return fieldsets

inlines = [InvoiceItemInline, InvoiceColumnInline, InvoiceTotalInline]

Expand Down
1 change: 1 addition & 0 deletions sage_invoice/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ class InvoiceConfig(AppConfig):

def ready(self) -> None:
import sage_invoice.check
import sage_invoice.signals
18 changes: 12 additions & 6 deletions sage_invoice/helpers/choice.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,25 @@ class InvoiceStatus(models.TextChoices):
UNPAID = ("unpaid", "UNPAID")


def get_template_choices():
"""
Dynamically generates the template choices based on the templates discovered by JinjaTemplateDiscovery.
Only the base name of the template is shown in the choices.
"""
def get_template_choices(is_receipt=False):
"""Dynamically generates the template choices based on the templates Only
the base name of the template is shown in the choices."""
template_discovery = JinjaTemplateDiscovery(
models_dir=getattr(settings, "MODEL_TEMPLATE", "sage_invoice")
)

templates = (
template_discovery.receipt_templates
if is_receipt
else template_discovery.model_templates
)

choices = [
(
key,
f"{os.path.basename(value).replace('.jinja2', '').replace('_', ' ').title()} Template",
)
for key, value in template_discovery.model_templates.items()
for key, value in templates.items()
]

return choices or [("", "No Templates Available")]
6 changes: 6 additions & 0 deletions sage_invoice/models/invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ class Invoice(TitleSlugMixin):
help_text=_("The current status of the invoice (Paid, Unpaid)."),
db_comment="Current status of the invoice (Paid, Unpaid)",
)
receipt = models.BooleanField(
verbose_name=_("Receipt"),
default=False,
help_text=_("Is this a receipt or an invoice"),
db_comment="Check if this invoice is a receipt",
)
notes = models.TextField(
verbose_name=_("Notes"),
blank=True,
Expand Down
1 change: 1 addition & 0 deletions sage_invoice/models/invoice_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class InvoiceItem(models.Model):
)
quantity = models.PositiveIntegerField(
verbose_name=_("Quantity"),
default=1,
help_text=_("The quantity of the item."),
db_comment="The quantity of the invoice item",
)
Expand Down
176 changes: 176 additions & 0 deletions sage_invoice/resource.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
from import_export import fields, resources
from import_export.widgets import BooleanWidget, DateWidget, ForeignKeyWidget

from sage_invoice.models import (
Invoice,
InvoiceCategory,
InvoiceColumn,
InvoiceItem,
InvoiceTotal,
)


class InvoiceItemResource(resources.ModelResource):
invoice = fields.Field(
column_name="invoice",
attribute="invoice",
widget=ForeignKeyWidget(Invoice, "id"),
)

class Meta:
model = InvoiceItem
fields = (
"id",
"invoice",
"description",
"quantity",
"unit_price",
"total_price",
)
import_id_fields = ["id"]
skip_unchanged = True
report_skipped = True


class InvoiceColumnResource(resources.ModelResource):
invoice = fields.Field(
column_name="invoice",
attribute="invoice",
widget=ForeignKeyWidget(Invoice, "id"),
)

class Meta:
model = InvoiceColumn
fields = ("id", "invoice", "header", "value")
import_id_fields = ["id"]
skip_unchanged = True
report_skipped = True


class InvoiceTotalResource(resources.ModelResource):
invoice = fields.Field(
column_name="invoice",
attribute="invoice",
widget=ForeignKeyWidget(Invoice, "id"),
)

class Meta:
model = InvoiceTotal
fields = (
"id",
"invoice",
"subtotal",
"tax_percentage",
"tax_amount",
"discount_percentage",
"discount_amount",
"total_amount",
)
import_id_fields = ["id"]
skip_unchanged = True
report_skipped = True


class InvoiceResource(resources.ModelResource):
category = fields.Field(
column_name="category",
attribute="category",
widget=ForeignKeyWidget(InvoiceCategory, "name"),
)

invoice_date = fields.Field(
column_name="invoice_date",
attribute="invoice_date",
widget=DateWidget(format="%Y-%m-%d"),
)

due_date = fields.Field(
column_name="due_date",
attribute="due_date",
widget=DateWidget(format="%Y-%m-%d"),
)

receipt = fields.Field(
column_name="receipt", attribute="receipt", widget=BooleanWidget()
)

# Define custom fields for related objects
items = fields.Field()
columns = fields.Field()
totals = fields.Field()

class Meta:
model = Invoice
fields = (
"id",
"title",
"invoice_date",
"customer_name",
"customer_email",
"status",
"receipt",
"category",
"due_date",
"tracking_code",
"notes",
"logo",
"signature",
"stamp",
"template_choice",
"items",
"columns",
"totals",
)
export_order = (
"id",
"title",
"invoice_date",
"customer_name",
"customer_email",
"status",
"receipt",
"category",
"due_date",
"tracking_code",
"notes",
"logo",
"signature",
"stamp",
"template_choice",
"items",
"columns",
"totals",
)
import_id_fields = ["id"]
skip_unchanged = True
report_skipped = True

def dehydrate_category(self, invoice):
category = invoice.category
if category:
return category.title
return ""

def dehydrate_items(self, invoice):
items = InvoiceItem.objects.filter(invoice=invoice)
return "; ".join(
[
f"{item.description} (Quantity: {item.quantity}, Unit Price: {item.unit_price}, Total: {item.total_price})"
for item in items
]
)

def dehydrate_columns(self, invoice):
columns = InvoiceColumn.objects.filter(invoice=invoice)
return "; ".join(
[f"{column.column_name}: {column.value}" for column in columns]
)

def dehydrate_totals(self, invoice):
totals = InvoiceTotal.objects.filter(invoice=invoice)
return "; ".join(
[
f"Subtotal: {total.subtotal}, Tax: {total.tax_amount} ({total.tax_percentage}%), Discount: {total.discount_amount} ({total.discount_percentage}%), Total: {total.total_amount}"
for total in totals
]
)
Loading

0 comments on commit e327dfd

Please sign in to comment.