-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
build(sage_invoice): Build the project and add sage_invoice app
- Loading branch information
1 parent
d3fa160
commit 4cdfab9
Showing
33 changed files
with
1,839 additions
and
0 deletions.
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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." | ||
), | ||
}, | ||
), | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"] |
Oops, something went wrong.