Skip to content

Commit

Permalink
build(sage_invoice): Build the project and add sage_invoice app
Browse files Browse the repository at this point in the history
  • Loading branch information
radinceorc committed Aug 27, 2024
1 parent d3fa160 commit 4cdfab9
Show file tree
Hide file tree
Showing 33 changed files with 1,839 additions and 0 deletions.
Empty file added sage_invoice/__init__.py
Empty file.
4 changes: 4 additions & 0 deletions sage_invoice/admin/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from .category import InvoiceCategoryAdmin
from .invoice import InvoiceAdmin

__all__ = ["InvoiceAdmin", "InvoiceCategoryAdmin"]
4 changes: 4 additions & 0 deletions sage_invoice/admin/actions/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from .download_html import export_as_html
from .show import show_invoice

__all__ = ["export_as_html", "show_invoice"]
62 changes: 62 additions & 0 deletions sage_invoice/admin/actions/download_html.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import os
import zipfile
from io import BytesIO

from django.conf import settings
from django.contrib import admin
from django.core.files.storage import default_storage
from django.http import HttpResponse

from sage_invoice.service.invoice_create import QuotationService


@admin.action(description="Download selected report as ZIP file")
def export_as_html(modeladmin, request, queryset):
service = QuotationService()

rendered_html = service.render_quotation(queryset)

zip_buffer = BytesIO()

with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zip_file:
# Write the rendered HTML to the ZIP file
html_filename = f"invoice_report_{queryset.first().id}.html"
zip_file.writestr(html_filename, rendered_html)

# Static files that should be included in the ZIP
static_files = {
"assets/css/style.css": "assets/css/style.css",
"assets/css/style2.css": "assets/css/style2.css",
"assets/js/jquery.min.js": "assets/js/jquery.min.js",
"assets/js/jspdf.min.js": "assets/js/jspdf.min.js",
"assets/js/html2canvas.min.js": "assets/js/html2canvas.min.js",
"assets/js/main.js": "assets/js/main.js",
}

for static_path, archive_path in static_files.items():
absolute_static_path = os.path.join(
settings.BASE_DIR, "media", "static", static_path
)
if os.path.exists(absolute_static_path):
with open(absolute_static_path, "rb") as static_file:
zip_file.writestr(archive_path, static_file.read())
else:
print(f"Static file not found: {absolute_static_path}")

invoice = queryset.first()

def add_image_to_zip(image_field, name):
if image_field:
with default_storage.open(image_field.name, "rb") as image_file:
zip_file.writestr(name, image_file.read())

add_image_to_zip(invoice.logo, "logo.png")
add_image_to_zip(invoice.signature, "signature.png")
add_image_to_zip(invoice.stamp, "stamp.png")

# Prepare the ZIP file for download
zip_filename = f"invoice_report_{queryset.first().id}.zip"
response = HttpResponse(zip_buffer.getvalue(), content_type="application/zip")
response["Content-Disposition"] = f"attachment; filename={zip_filename}"

return response
11 changes: 11 additions & 0 deletions sage_invoice/admin/actions/show.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from django.contrib import admin
from django.shortcuts import redirect
from django.urls import reverse


@admin.action(description="Show selected invoice")
def show_invoice(modeladmin, request, queryset):
invoice = queryset.first()
if invoice:
url = reverse("invoice_detail", kwargs={"invoice_slug": invoice.slug})
return redirect(url)
30 changes: 30 additions & 0 deletions sage_invoice/admin/category.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from django.contrib import admin
from django.utils.translation import gettext_lazy as _

from sage_invoice.models import InvoiceCategory


@admin.register(InvoiceCategory)
class InvoiceCategoryAdmin(admin.ModelAdmin):
admin_priority = 2
list_display = ("title", "description")
search_fields = ("title", "description")
readonly_fields = ("slug",)
ordering = ("title",)
save_on_top = True

fieldsets = (
(
_("Category Details"),
{
"fields": (
"title",
"slug",
"description",
),
"description": _(
"Details of the invoice category including title and description."
),
},
),
)
96 changes: 96 additions & 0 deletions sage_invoice/admin/invoice.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
from django.contrib import admin
from django.utils.translation import gettext_lazy as _

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


class InvoiceItemInline(admin.TabularInline):
model = InvoiceItem
extra = 1
readonly_fields = ("total_price",)


class InvoiceColumnInline(admin.TabularInline):
model = InvoiceColumn
extra = 1


class InvoiceTotalInline(admin.TabularInline):
model = InvoiceTotal
extra = 1
readonly_fields = ("subtotal", "tax_amount", "discount_amount", "total_amount")


@admin.register(Invoice)
class InvoiceAdmin(admin.ModelAdmin):
admin_priority = 1
list_display = ("title", "invoice_date", "customer_name", "status")
search_fields = ("customer_name", "status", "customer_email")
autocomplete_fields = ("category",)
save_on_top = True
list_filter = ("status", "invoice_date", "category")
ordering = ("-invoice_date",)
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."
),
},
),
)

inlines = [InvoiceItemInline, InvoiceColumnInline, InvoiceTotalInline]

def get_inline_instances(self, request, obj=None):
inlines = []
if obj and obj.pk:
inlines = [
InvoiceItemInline(self.model, self.admin_site),
InvoiceColumnInline(self.model, self.admin_site),
InvoiceTotalInline(self.model, self.admin_site),
]
else:
inlines = [
InvoiceItemInline(self.model, self.admin_site),
InvoiceTotalInline(self.model, self.admin_site),
]
return inlines
9 changes: 9 additions & 0 deletions sage_invoice/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from django.apps import AppConfig


class InvoiceConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "sage_invoice"

def ready(self) -> None:
import sage_invoice.check
68 changes: 68 additions & 0 deletions sage_invoice/check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
from importlib.util import find_spec

from django.conf import settings
from django.core.checks import Error, register


@register()
def check_installed_apps(app_configs, **kwargs):
errors = []
required_apps = [
"sage_invoice",
]

for app in required_apps:
if app not in settings.INSTALLED_APPS:
errors.append(
Error(
f"'{app}' is missing in INSTALLED_APPS.",
hint=f"Add '{app}' to INSTALLED_APPS in your settings.",
obj=settings,
id=f"sage_invoice.E00{required_apps.index(app) + 1}",
)
)

return errors


@register()
def check_required_libraries(app_configs, **kwargs):
errors = []
required_libraries = [
"sage_invoice",
]

for library in required_libraries:
if not find_spec(library):
errors.append(
Error(
f"The required library '{library}' is not installed.",
hint=f"Install '{library}' via pip: pip install {library}",
obj=settings,
id=f"sage_invoice.E00{required_libraries.index(library) + 2}",
)
)

return errors


@register()
def check_required_settings(app_configs, **kwargs):
errors = []

required_settings = [
"MODEL_PREFIX",
"MODEL_TEMPLATE",
]

for setting in required_settings:
if not hasattr(settings, setting):
errors.append(
Error(
f"The required setting '{setting}' is not defined.",
hint=f"Define '{setting}' in your settings.py.",
obj=settings,
id=f"sage_invoice.E00{required_settings.index(setting) + 3}",
)
)
return errors
Empty file.
29 changes: 29 additions & 0 deletions sage_invoice/helpers/choice.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import os

from django.conf import settings
from django.db import models

from sage_invoice.service.discovery import JinjaTemplateDiscovery


class InvoiceStatus(models.TextChoices):
PAID = ("paid", "PAID")
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.
"""
template_discovery = JinjaTemplateDiscovery(
models_dir=getattr(settings, "MODEL_TEMPLATE", "sage_invoice")
)
choices = [
(
key,
f"{os.path.basename(value).replace('.jinja2', '').replace('_', ' ').title()} Template",
)
for key, value in template_discovery.model_templates.items()
]
return choices or [("", "No Templates Available")]
Empty file.
7 changes: 7 additions & 0 deletions sage_invoice/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from .invoice import Invoice
from .invoice_category import InvoiceCategory
from .invoice_column import InvoiceColumn
from .invoice_item import InvoiceItem
from .invoice_total import InvoiceTotal

__all__ = ["Invoice", "InvoiceColumn", "InvoiceItem", "InvoiceTotal", "InvoiceCategory"]
Loading

0 comments on commit 4cdfab9

Please sign in to comment.