add po
This commit is contained in:
parent
914b98a66b
commit
2a397c3a2b
@ -14,8 +14,9 @@ from .mixins import AddClassMixin
|
||||
from django_ledger.forms.invoice import (
|
||||
InvoiceModelCreateForm as InvoiceModelCreateFormBase,
|
||||
)
|
||||
|
||||
from django.forms.models import inlineformset_factory
|
||||
from django_ledger.forms.bill import BillModelCreateForm as BillModelCreateFormBase
|
||||
|
||||
from django_ledger.forms.journal_entry import (
|
||||
JournalEntryModelCreateForm as JournalEntryModelCreateFormBase,
|
||||
)
|
||||
@ -42,6 +43,8 @@ from .models import (
|
||||
Activity,
|
||||
Notes,
|
||||
CarModel,
|
||||
CarSerie,
|
||||
CarTrim,
|
||||
SaleOrder,
|
||||
CarMake,
|
||||
Customer,
|
||||
@ -1899,3 +1902,29 @@ class StaffTaskForm(forms.ModelForm):
|
||||
widgets = {
|
||||
"due_date": forms.DateTimeInput(attrs={"type": "date"}),
|
||||
}
|
||||
|
||||
|
||||
#############################################################
|
||||
|
||||
class ItemInventoryForm(forms.Form):
|
||||
make = forms.ModelChoiceField(
|
||||
queryset=CarMake.objects.all(),
|
||||
widget=forms.Select(attrs={"class": "form-control", "id": "make"}),
|
||||
label=_("Make"),
|
||||
)
|
||||
model = forms.ModelChoiceField(
|
||||
queryset=CarModel.objects.none(),
|
||||
widget=forms.Select(attrs={"class": "form-control", "id": "model"}),
|
||||
label=_("Model"),
|
||||
)
|
||||
serie = forms.ModelChoiceField(
|
||||
queryset=CarSerie.objects.none(),
|
||||
widget=forms.Select(attrs={"class": "form-control", "id": "serie"}),
|
||||
label=_("Serie"),
|
||||
)
|
||||
trim = forms.ModelChoiceField(
|
||||
queryset=CarTrim.objects.none(),
|
||||
widget=forms.Select(attrs={"class": "form-control", "id": "trim"}),
|
||||
label=_("Trim"),
|
||||
)
|
||||
|
||||
|
||||
@ -0,0 +1,63 @@
|
||||
# Generated by Django 5.2.1 on 2025-06-03 11:03
|
||||
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('inventory', '0014_alter_opportunity_amount'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='IntendedVehicle',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('year', models.PositiveIntegerField()),
|
||||
('color', models.CharField(max_length=30)),
|
||||
('engine', models.CharField(blank=True, max_length=50, null=True)),
|
||||
('condition', models.CharField(choices=[('new', 'New'), ('used', 'Used'), ('certified', 'Certified Pre-Owned')], max_length=20)),
|
||||
('expected_cost', models.DecimalField(decimal_places=2, max_digits=12)),
|
||||
('make', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.carmake')),
|
||||
('model', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.carmodel')),
|
||||
('serie', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.carserie')),
|
||||
('trim', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.cartrim')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PurchaseOrder',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('po_number', models.CharField(editable=False, max_length=50, unique=True)),
|
||||
('status', models.CharField(choices=[('pending', 'Pending'), ('approved', 'Approved'), ('rejected', 'Rejected'), ('completed', 'Completed'), ('canceled', 'Canceled')], default='pending', max_length=20)),
|
||||
('quantity', models.PositiveIntegerField(default=1)),
|
||||
('total_cost', models.DecimalField(decimal_places=2, max_digits=12)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('expected_delivery_date', models.DateField(blank=True, null=True)),
|
||||
('notes', models.TextField(blank=True, null=True)),
|
||||
('approved_at', models.DateTimeField(blank=True, null=True)),
|
||||
('approved_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='approved_orders', to=settings.AUTH_USER_MODEL)),
|
||||
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_orders', to=settings.AUTH_USER_MODEL)),
|
||||
('intended_vehicle', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.intendedvehicle')),
|
||||
('supplier', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.vendor')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DeliveryReceipt',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('receipt_number', models.CharField(editable=False, max_length=50, unique=True)),
|
||||
('received_at', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
('notes', models.TextField(blank=True, null=True)),
|
||||
('car', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='inventory.car')),
|
||||
('received_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
|
||||
('purchase_order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.purchaseorder')),
|
||||
],
|
||||
),
|
||||
]
|
||||
35
inventory/migrations/0016_purchaseorderitem_sale.py
Normal file
35
inventory/migrations/0016_purchaseorderitem_sale.py
Normal file
@ -0,0 +1,35 @@
|
||||
# Generated by Django 5.2.1 on 2025-06-03 11:21
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('inventory', '0015_intendedvehicle_purchaseorder_deliveryreceipt'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='PurchaseOrderItem',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('quantity', models.PositiveIntegerField(default=1)),
|
||||
('price', models.DecimalField(decimal_places=2, max_digits=10)),
|
||||
('purchase_order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.purchaseorder')),
|
||||
('vehicle', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.intendedvehicle')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Sale',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('sale_date', models.DateTimeField(auto_now_add=True)),
|
||||
('selling_price', models.DecimalField(decimal_places=2, max_digits=12)),
|
||||
('car', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.car')),
|
||||
('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.customer')),
|
||||
('salesperson', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='inventory.staff')),
|
||||
],
|
||||
),
|
||||
]
|
||||
20
inventory/migrations/0017_intendedvehicle_purchase_order.py
Normal file
20
inventory/migrations/0017_intendedvehicle_purchase_order.py
Normal file
@ -0,0 +1,20 @@
|
||||
# Generated by Django 5.2.1 on 2025-06-03 11:36
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('inventory', '0016_purchaseorderitem_sale'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='intendedvehicle',
|
||||
name='purchase_order',
|
||||
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='intended_vehicles', to='inventory.purchaseorder'),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
18
inventory/migrations/0018_intendedvehicle_quantity.py
Normal file
18
inventory/migrations/0018_intendedvehicle_quantity.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.2.1 on 2025-06-03 13:56
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('inventory', '0017_intendedvehicle_purchase_order'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='intendedvehicle',
|
||||
name='quantity',
|
||||
field=models.PositiveIntegerField(default=1),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,33 @@
|
||||
# Generated by Django 5.2.1 on 2025-06-03 17:42
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('inventory', '0018_intendedvehicle_quantity'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='intendedvehicle',
|
||||
name='make',
|
||||
field=models.CharField(max_length=30),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='intendedvehicle',
|
||||
name='model',
|
||||
field=models.CharField(max_length=30),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='intendedvehicle',
|
||||
name='serie',
|
||||
field=models.CharField(blank=True, max_length=30, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='intendedvehicle',
|
||||
name='trim',
|
||||
field=models.CharField(blank=True, max_length=30, null=True),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,68 @@
|
||||
# Generated by Django 5.2.1 on 2025-06-04 13:33
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('inventory', '0019_alter_intendedvehicle_make_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='intendedvehicle',
|
||||
name='purchase_order',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='purchaseorder',
|
||||
name='intended_vehicle',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='purchaseorderitem',
|
||||
name='vehicle',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='purchaseorder',
|
||||
name='approved_by',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='purchaseorder',
|
||||
name='created_by',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='purchaseorder',
|
||||
name='supplier',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='purchaseorderitem',
|
||||
name='purchase_order',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='sale',
|
||||
name='car',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='sale',
|
||||
name='customer',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='sale',
|
||||
name='salesperson',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='DeliveryReceipt',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='IntendedVehicle',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='PurchaseOrder',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='PurchaseOrderItem',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='Sale',
|
||||
),
|
||||
]
|
||||
@ -29,7 +29,7 @@ from django_ledger.models import (
|
||||
EstimateModel,
|
||||
InvoiceModel,
|
||||
AccountModel,
|
||||
EntityManagementModel,
|
||||
EntityManagementModel
|
||||
)
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
@ -2855,3 +2855,9 @@ class PaymentHistory(models.Model):
|
||||
|
||||
def is_successful(self):
|
||||
return self.status == self.COMPLETED
|
||||
|
||||
|
||||
|
||||
######################################################################################################
|
||||
######################################################################################################
|
||||
######################################################################################################
|
||||
|
||||
@ -363,4 +363,20 @@ def currency_format(value):
|
||||
|
||||
@register.filter
|
||||
def filter_by_role(accounts, role_prefix):
|
||||
return [account for account in accounts if account.role.startswith(role_prefix)]
|
||||
return [account for account in accounts if account.role.startswith(role_prefix)]
|
||||
|
||||
@register.inclusion_tag('purchase_orders/tags/po_item_table.html', takes_context=True)
|
||||
def po_item_table1(context, queryset):
|
||||
return {
|
||||
'entity_slug': context['entity_slug'],
|
||||
'po_model': context['po_model'],
|
||||
'po_item_list': queryset
|
||||
}
|
||||
|
||||
@register.inclusion_tag('purchase_orders/includes/po_item_formset.html', takes_context=True)
|
||||
def po_item_formset_table(context, po_model, itemtxs_formset):
|
||||
return {
|
||||
'entity_slug': context['view'].kwargs['entity_slug'],
|
||||
'po_model': po_model,
|
||||
'itemtxs_formset': itemtxs_formset,
|
||||
}
|
||||
|
||||
@ -693,7 +693,8 @@ path(
|
||||
),
|
||||
# Bills
|
||||
path("items/bills/", views.BillListView.as_view(), name="bill_list"),
|
||||
path("items/bills/create/", views.bill_create, name="bill_create"),
|
||||
path("items/bills/create/", views.BillModelCreateViewView.as_view(), name="bill_create"),
|
||||
# path("items/bills/create/", views.bill_create, name="bill_create"),
|
||||
path(
|
||||
"items/bills/<uuid:pk>/bill_detail/",
|
||||
views.BillDetailView.as_view(),
|
||||
@ -796,7 +797,7 @@ path(
|
||||
#dashboard api
|
||||
path('entity/<slug:entity_slug>/data/net-payables/',
|
||||
views.PayableNetAPIView.as_view(),
|
||||
name='entity-json-net-payables'),
|
||||
name='entity-json-net-payables'),
|
||||
path('entity/<slug:entity_slug>/data/net-receivables/',
|
||||
views.ReceivableNetAPIView.as_view(),
|
||||
name='entity-json-net-receivables'),
|
||||
@ -808,8 +809,41 @@ path(
|
||||
path('management/user_management/', views.user_management, name='user_management'),
|
||||
path('management/<str:content_type>/<slug:slug>/activate_account/', views.activate_account, name='activate_account'),
|
||||
path('management/<str:content_type>/<slug:slug>/permenant_delete_account/', views.permenant_delete_account, name='permenant_delete_account'),
|
||||
]
|
||||
#########
|
||||
# Purchase Order
|
||||
path('purchase_orders/', views.PurchaseOrderListView.as_view(), name='purchase_order_list'),
|
||||
path('purchase_orders/new/', views.PurchaseOrderCreateView, name='purchase_order_create'),
|
||||
path('purchase_orders/<uuid:pk>/detail/', views.PurchaseOrderDetailView.as_view(), name='purchase_order_detail'),
|
||||
path('purchase_orders/<slug:entity_slug>/<uuid:po_pk>/update/', views.PurchaseOrderUpdateView.as_view(), name='purchase_order_update'),
|
||||
path('purchase_orders/<slug:entity_slug>/update/<uuid:po_pk>/update-items/',
|
||||
views.PurchaseOrderUpdateView.as_view(action_update_items=True),
|
||||
name='purchase_order_update_items'),
|
||||
path('purchase_orders/inventory_item/<uuid:pk>/create/', views.InventoryItemCreateView, name='inventory_item_create'),
|
||||
path('purchase_orders/<uuid:po_pk>/inventory_items_filter/', views.inventory_items_filter, name='inventory_items_filter'),
|
||||
path('purchase_orders/<slug:entity_slug>/delete/<uuid:po_pk>/',
|
||||
views.PurchaseOrderModelDeleteView.as_view(),
|
||||
name='po-delete'),
|
||||
|
||||
# Actions....
|
||||
path('<slug:entity_slug>/action/<uuid:po_pk>/mark-as-draft/',
|
||||
views.PurchaseOrderMarkAsDraftView.as_view(),
|
||||
name='po-action-mark-as-draft'),
|
||||
path('<slug:entity_slug>/action/<uuid:po_pk>/mark-as-review/',
|
||||
views.PurchaseOrderMarkAsReviewView.as_view(),
|
||||
name='po-action-mark-as-review'),
|
||||
path('<slug:entity_slug>/action/<uuid:po_pk>/mark-as-approved/',
|
||||
views.PurchaseOrderMarkAsApprovedView.as_view(),
|
||||
name='po-action-mark-as-approved'),
|
||||
path('<slug:entity_slug>/action/<uuid:po_pk>/mark-as-fulfilled/',
|
||||
views.PurchaseOrderMarkAsFulfilledView.as_view(),
|
||||
name='po-action-mark-as-fulfilled'),
|
||||
path('<slug:entity_slug>/action/<uuid:po_pk>/mark-as-canceled/',
|
||||
views.PurchaseOrderMarkAsCanceledView.as_view(),
|
||||
name='po-action-mark-as-canceled'),
|
||||
path('<slug:entity_slug>/action/<uuid:po_pk>/mark-as-void/',
|
||||
views.PurchaseOrderMarkAsVoidView.as_view(),
|
||||
name='po-action-mark-as-void'),
|
||||
]
|
||||
|
||||
handler404 = "inventory.views.custom_page_not_found_view"
|
||||
handler500 = "inventory.views.custom_error_view"
|
||||
|
||||
@ -5,7 +5,6 @@ import logging
|
||||
from datetime import datetime
|
||||
from time import sleep
|
||||
import numpy as np
|
||||
|
||||
# from rich import print
|
||||
from random import randint
|
||||
from decimal import Decimal
|
||||
@ -20,6 +19,7 @@ from django.db import IntegrityError
|
||||
from background_task.models import Task
|
||||
from django.db.models.deletion import RestrictedError
|
||||
from django.http.response import StreamingHttpResponse
|
||||
from django.core.exceptions import ImproperlyConfigured, ValidationError
|
||||
|
||||
# Django
|
||||
from django.db.models import Q
|
||||
@ -83,6 +83,10 @@ from django_ledger.forms.bank_account import (
|
||||
BankAccountCreateForm,
|
||||
BankAccountUpdateForm,
|
||||
)
|
||||
from django_ledger.views.bill import (
|
||||
BillModelCreateView
|
||||
# BillModelUpdateView as BillModelUpdateViewBase
|
||||
)
|
||||
from django_ledger.forms.bill import (
|
||||
ApprovedBillModelUpdateForm,
|
||||
InReviewBillModelUpdateForm,
|
||||
@ -92,6 +96,19 @@ from django_ledger.forms.invoice import (
|
||||
ApprovedInvoiceModelUpdateForm,
|
||||
PaidInvoiceModelUpdateForm,
|
||||
)
|
||||
from django_ledger.forms.item import (
|
||||
InventoryItemCreateForm,
|
||||
)
|
||||
from django_ledger.forms.purchase_order import (PurchaseOrderModelCreateForm, BasePurchaseOrderModelUpdateForm,
|
||||
DraftPurchaseOrderModelUpdateForm, ReviewPurchaseOrderModelUpdateForm,
|
||||
ApprovedPurchaseOrderModelUpdateForm,
|
||||
get_po_itemtxs_formset_class)
|
||||
from django_ledger.views.purchase_order import (
|
||||
PurchaseOrderModelDetailView as PurchaseOrderModelDetailViewBase,
|
||||
PurchaseOrderModelUpdateView as PurchaseOrderModelUpdateViewBase,
|
||||
BasePurchaseOrderActionActionView as BasePurchaseOrderActionActionViewBase,
|
||||
PurchaseOrderModelDeleteView as PurchaseOrderModelDeleteViewBase
|
||||
)
|
||||
from django_ledger.models import (
|
||||
ItemTransactionModel,
|
||||
EntityModel,
|
||||
@ -105,6 +122,7 @@ from django_ledger.models import (
|
||||
ItemModel,
|
||||
BillModel,
|
||||
LedgerModel,
|
||||
PurchaseOrderModel
|
||||
)
|
||||
from django_ledger.views.financial_statement import (
|
||||
FiscalYearBalanceSheetView,
|
||||
@ -6263,6 +6281,7 @@ def bill_mark_as_paid(request, pk):
|
||||
return redirect("bill_detail", pk=bill.pk)
|
||||
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required("django_ledger.add_billmodel", raise_exception=True)
|
||||
def bill_create(request):
|
||||
@ -8275,3 +8294,318 @@ def permenant_delete_account(request, content_type, slug):
|
||||
return render(
|
||||
request, "admin_management/permenant_delete_account.html", {"obj": obj}
|
||||
)
|
||||
|
||||
|
||||
#####################################################################
|
||||
|
||||
|
||||
def PurchaseOrderCreateView(request):
|
||||
dealer = get_user_type(request)
|
||||
entity = dealer.entity
|
||||
if(request.method == "POST"):
|
||||
po = entity.create_purchase_order(po_title=request.POST.get("po_title"))
|
||||
po.entity = entity
|
||||
po.save()
|
||||
messages.success(request, _("Purchase order created successfully"))
|
||||
return redirect('purchase_order_detail', pk=po.pk)
|
||||
|
||||
form = PurchaseOrderModelCreateForm(entity_slug=entity.slug, user_model=entity.admin)
|
||||
return render(request, "purchase_orders/po_form.html", {"form": form})
|
||||
|
||||
def InventoryItemCreateView(request,pk):
|
||||
po = get_object_or_404(PurchaseOrderModel, pk=pk)
|
||||
dealer = get_user_type(request)
|
||||
entity = dealer.entity
|
||||
coa = entity.get_default_coa()
|
||||
inventory_accounts = entity.get_coa_all().get(name='ASSET_CA_INVENTORY')
|
||||
if(request.method == "POST"):
|
||||
make = request.POST.get("make")
|
||||
model = request.POST.get("model")
|
||||
serie = request.POST.get("serie")
|
||||
trim = request.POST.get("trim")
|
||||
|
||||
make_name = models.CarMake.objects.get(pk=make)
|
||||
model_name = models.CarModel.objects.get(pk=model)
|
||||
serie_name = models.CarSerie.objects.get(pk=serie)
|
||||
trim_name = models.CarTrim.objects.get(pk=trim)
|
||||
|
||||
inventory_name = f"{make_name.name} - {model_name.name} - {serie_name.name} - {trim_name.name}"
|
||||
uom = entity.get_uom_all().get(name='Unit')
|
||||
entity.create_item_inventory(
|
||||
name=inventory_name,
|
||||
uom_model=uom,
|
||||
item_type=ItemModel.ITEM_TYPE_MATERIAL
|
||||
)
|
||||
messages.success(request, _("Inventory item created successfully"))
|
||||
return redirect('purchase_order_detail', pk=po.pk)
|
||||
|
||||
|
||||
class PurchaseOrderDetailView(PurchaseOrderModelDetailViewBase):
|
||||
template_name = 'purchase_orders/po_detail.html'
|
||||
context_object_name = 'po_model'
|
||||
|
||||
|
||||
def get_queryset(self):
|
||||
dealer = get_user_type(self.request)
|
||||
self.queryset = PurchaseOrderModel.objects.for_entity(
|
||||
entity_slug=dealer.entity.slug,
|
||||
user_model=dealer.entity.admin
|
||||
).select_related('entity', 'ce_model')
|
||||
return super().get_queryset()
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
dealer = get_user_type(self.request)
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['entity_slug'] = dealer.entity.slug
|
||||
return context
|
||||
|
||||
|
||||
def inventory_items_filter(request,po_pk):
|
||||
dealer = get_user_type(request)
|
||||
make = request.GET.get('make')
|
||||
model = request.GET.get('model')
|
||||
serie = request.GET.get('serie')
|
||||
make_data = models.CarMake.objects.all()
|
||||
model_data = models.CarModel.objects.none()
|
||||
serie_data = models.CarSerie.objects.none()
|
||||
trim_data = models.CarTrim.objects.none()
|
||||
if make:
|
||||
make = models.CarMake.objects.get(pk=make)
|
||||
model_data = make.carmodel_set.all()
|
||||
elif model:
|
||||
model = models.CarModel.objects.get(pk=model)
|
||||
serie_data = model.carserie_set.all()
|
||||
elif serie:
|
||||
serie = models.CarSerie.objects.get(pk=serie)
|
||||
trim_data = serie.cartrim_set.all()
|
||||
context = {
|
||||
'make_data': make_data,
|
||||
'model_data': model_data,
|
||||
'serie_data': serie_data,
|
||||
'trim_data': trim_data,
|
||||
'inventory_accounts': dealer.entity.get_coa_accounts().filter(role="asset_ca_inv"),
|
||||
'inventory_items': dealer.entity.get_items_inventory(),
|
||||
'entity_slug': dealer.entity.slug,
|
||||
'po_model': get_object_or_404(PurchaseOrderModel, pk=po_pk)
|
||||
}
|
||||
return render(request, "purchase_orders/po_detail.html", context)
|
||||
|
||||
|
||||
# def PurchaseOrderDetailView(request, pk):
|
||||
# po = get_object_or_404(PurchaseOrderModel, pk=pk)
|
||||
# dealer = get_user_type(request)
|
||||
|
||||
# make = request.GET.get('make')
|
||||
# model = request.GET.get('model')
|
||||
# serie = request.GET.get('serie')
|
||||
# make_data = models.CarMake.objects.all()
|
||||
# model_data = models.CarModel.objects.none()
|
||||
# serie_data = models.CarSerie.objects.none()
|
||||
# trim_data = models.CarTrim.objects.none()
|
||||
# if make:
|
||||
# make = models.CarMake.objects.get(pk=make)
|
||||
# model_data = make.carmodel_set.all()
|
||||
# elif model:
|
||||
# model = models.CarModel.objects.get(pk=model)
|
||||
# serie_data = model.carserie_set.all()
|
||||
# elif serie:
|
||||
# serie = models.CarSerie.objects.get(pk=serie)
|
||||
# trim_data = serie.cartrim_set.all()
|
||||
# context = {
|
||||
# 'make_data': make_data,
|
||||
# 'model_data': model_data,
|
||||
# 'serie_data': serie_data,
|
||||
# 'trim_data': trim_data,
|
||||
# 'po': po,
|
||||
# 'inventory_accounts': dealer.entity.get_coa_accounts().filter(role="asset_ca_inv"),
|
||||
# 'inventory_items': dealer.entity.get_items_inventory(),
|
||||
# }
|
||||
# return render(request, "purchase_orders/po_detail.html", context)
|
||||
|
||||
|
||||
class PurchaseOrderListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
||||
model = PurchaseOrderModel
|
||||
context_object_name = "purchase_orders"
|
||||
paginate_by = 30
|
||||
template_name = "purchase_orders/po_list.html"
|
||||
permission_required = ["inventory.view_carfinance"]
|
||||
|
||||
def get_queryset(self):
|
||||
dealer = get_user_type(self.request)
|
||||
entity = dealer.entity
|
||||
return self.model.objects.filter(entity=entity)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
dealer = get_user_type(self.request)
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['entity_slug'] = dealer.entity.slug
|
||||
return context
|
||||
|
||||
class PurchaseOrderUpdateView(PurchaseOrderModelUpdateViewBase):
|
||||
template_name = 'purchase_orders/po_update.html'
|
||||
context_object_name = 'po_model'
|
||||
def get_context_data(self, **kwargs):
|
||||
dealer = get_user_type(self.request)
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['entity_slug'] = dealer.entity.slug
|
||||
context['make_data'] = models.CarMake.objects.all()
|
||||
context['model_data'] = models.CarModel.objects.none()
|
||||
context['serie_data'] = models.CarSerie.objects.none()
|
||||
context['trim_data'] = models.CarTrim.objects.none()
|
||||
return context
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('purchase_order_update',
|
||||
kwargs={
|
||||
'entity_slug': self.kwargs['entity_slug'],
|
||||
'po_pk': self.kwargs['po_pk']
|
||||
})
|
||||
def get(self, request, entity_slug, po_pk, *args, **kwargs):
|
||||
if self.action_update_items:
|
||||
return HttpResponseRedirect(
|
||||
redirect_to=reverse('purchase_order_update',
|
||||
kwargs={
|
||||
'entity_slug': entity_slug,
|
||||
'po_pk': po_pk
|
||||
})
|
||||
)
|
||||
return super(PurchaseOrderModelUpdateViewBase, self).get(request, entity_slug, po_pk, *args, **kwargs)
|
||||
|
||||
def post(self, request, entity_slug, *args, **kwargs):
|
||||
dealer = get_user_type(self.request)
|
||||
if self.action_update_items:
|
||||
|
||||
if not request.user.is_authenticated:
|
||||
return HttpResponseForbidden()
|
||||
|
||||
queryset = self.get_queryset()
|
||||
po_model: PurchaseOrderModel = self.get_object(queryset=queryset)
|
||||
self.object = po_model
|
||||
po_itemtxs_formset_class = get_po_itemtxs_formset_class(po_model)
|
||||
itemtxs_formset = po_itemtxs_formset_class(request.POST,
|
||||
user_model=dealer.entity.admin,
|
||||
po_model=po_model,
|
||||
entity_slug=entity_slug)
|
||||
|
||||
if itemtxs_formset.has_changed():
|
||||
if itemtxs_formset.is_valid():
|
||||
itemtxs_list = itemtxs_formset.save(commit=False)
|
||||
create_bill_uuids = [
|
||||
str(i['uuid'].uuid) for i in itemtxs_formset.cleaned_data if i and i['create_bill'] is True
|
||||
]
|
||||
|
||||
if create_bill_uuids:
|
||||
item_uuids = ','.join(create_bill_uuids)
|
||||
redirect_url = reverse(
|
||||
'django_ledger:bill-create-po',
|
||||
kwargs={
|
||||
'entity_slug': self.kwargs['entity_slug'],
|
||||
'po_pk': po_model.uuid,
|
||||
}
|
||||
)
|
||||
redirect_url += f'?item_uuids={item_uuids}'
|
||||
return HttpResponseRedirect(redirect_url)
|
||||
|
||||
for itemtxs in itemtxs_list:
|
||||
if not itemtxs.po_model_id:
|
||||
itemtxs.po_model_id = po_model.uuid
|
||||
itemtxs.clean()
|
||||
|
||||
itemtxs_list = itemtxs_formset.save()
|
||||
po_model.update_state()
|
||||
po_model.clean()
|
||||
po_model.save(update_fields=['po_amount',
|
||||
'po_amount_received',
|
||||
'updated'])
|
||||
# if valid get saved formset from DB
|
||||
messages.add_message(request, messages.SUCCESS, 'PO items updated successfully.')
|
||||
return self.render_to_response(context=self.get_context_data())
|
||||
# if not valid, return formset with errors...
|
||||
return self.render_to_response(context=self.get_context_data(itemtxs_formset=itemtxs_formset))
|
||||
return super(PurchaseOrderUpdateView, self).post(request,entity_slug, *args, **kwargs)
|
||||
|
||||
def get_form(self, form_class=None):
|
||||
po_model: PurchaseOrderModel = self.object
|
||||
if po_model.is_draft():
|
||||
return DraftPurchaseOrderModelUpdateForm(
|
||||
entity_slug=self.kwargs['entity_slug'],
|
||||
user_model=self.request.user,
|
||||
**self.get_form_kwargs()
|
||||
)
|
||||
elif po_model.is_review():
|
||||
return ReviewPurchaseOrderModelUpdateForm(
|
||||
entity_slug=self.kwargs['entity_slug'],
|
||||
user_model=self.request.user,
|
||||
**self.get_form_kwargs()
|
||||
)
|
||||
elif po_model.is_approved():
|
||||
return ApprovedPurchaseOrderModelUpdateForm(
|
||||
entity_slug=self.kwargs['entity_slug'],
|
||||
user_model=self.request.user,
|
||||
**self.get_form_kwargs()
|
||||
)
|
||||
return BasePurchaseOrderModelUpdateForm(
|
||||
entity_slug=self.kwargs['entity_slug'],
|
||||
user_model=self.request.user,
|
||||
**self.get_form_kwargs()
|
||||
)
|
||||
|
||||
|
||||
class BasePurchaseOrderActionActionView(BasePurchaseOrderActionActionViewBase):
|
||||
def get_redirect_url(self, entity_slug, po_pk, *args, **kwargs):
|
||||
return reverse('purchase_order_update',
|
||||
kwargs={
|
||||
'entity_slug': entity_slug,
|
||||
'po_pk': po_pk
|
||||
})
|
||||
def get(self, request, *args, **kwargs):
|
||||
kwargs['user_model'] = self.request.user
|
||||
if not self.action_name:
|
||||
raise ImproperlyConfigured('View attribute action_name is required.')
|
||||
response = super(BasePurchaseOrderActionActionView, self).get(request, *args, **kwargs)
|
||||
po_model: PurchaseOrderModel = self.get_object()
|
||||
|
||||
try:
|
||||
getattr(po_model, self.action_name)(commit=self.commit, **kwargs)
|
||||
except ValidationError as e:
|
||||
messages.add_message(request,
|
||||
message=e.message,
|
||||
level=messages.ERROR,
|
||||
)
|
||||
return response
|
||||
|
||||
class PurchaseOrderModelDeleteView(PurchaseOrderModelDeleteViewBase):
|
||||
template_name = 'purchase_orders/po_delete.html'
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('purchase_order_list')
|
||||
|
||||
|
||||
class PurchaseOrderMarkAsDraftView(BasePurchaseOrderActionActionView):
|
||||
action_name = 'mark_as_draft'
|
||||
|
||||
|
||||
class PurchaseOrderMarkAsReviewView(BasePurchaseOrderActionActionView):
|
||||
action_name = 'mark_as_review'
|
||||
|
||||
|
||||
class PurchaseOrderMarkAsApprovedView(BasePurchaseOrderActionActionView):
|
||||
action_name = 'mark_as_approved'
|
||||
|
||||
|
||||
class PurchaseOrderMarkAsFulfilledView(BasePurchaseOrderActionActionView):
|
||||
action_name = 'mark_as_fulfilled'
|
||||
|
||||
|
||||
class PurchaseOrderMarkAsCanceledView(BasePurchaseOrderActionActionView):
|
||||
action_name = 'mark_as_canceled'
|
||||
|
||||
|
||||
class PurchaseOrderMarkAsVoidView(BasePurchaseOrderActionActionView):
|
||||
action_name = 'mark_as_void'
|
||||
|
||||
|
||||
##############################bil
|
||||
|
||||
class BillModelCreateViewView(BillModelCreateView):
|
||||
template_name = 'ledger/bills/bill_form.html'
|
||||
49
templates/bill/bill_create.html
Normal file
49
templates/bill/bill_create.html
Normal file
@ -0,0 +1,49 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load django_ledger %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-6">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-light py-3">
|
||||
<h2 class="h4 text-center mb-0">{% trans 'Create Bill' %}</h2>
|
||||
</div>
|
||||
<form action="{{ form_action_url }}" method="post" id="djl-bill-model-create-form-id">
|
||||
<div class="card-body">
|
||||
|
||||
{% csrf_token %}
|
||||
{% if po_model %}
|
||||
<div class="text-center mb-4">
|
||||
<h3 class="h5">{% trans 'Bill for' %} {{ po_model.po_number }}</h3>
|
||||
<p class="text-muted mb-3">{% trans 'Bill for' %} {{ po_model.po_title }}</p>
|
||||
<div class="d-flex flex-column gap-2">
|
||||
{% for itemtxs in po_itemtxs_qs %}
|
||||
<span class="badge bg-secondary">{{ itemtxs }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="mb-4">
|
||||
{{ form|add_class:"form-control" }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-footer bg-transparent">
|
||||
<div class="d-grid gap-2">
|
||||
<button type="submit"
|
||||
id="djl-bill-create-button"
|
||||
class="btn btn-primary btn-lg">{% trans 'Create' %}
|
||||
</button>
|
||||
<a href="{% url 'django_ledger:bill-list' entity_slug=view.kwargs.entity_slug %}"
|
||||
id="djl-bill-create-back-button"
|
||||
class="btn btn-outline-secondary">{% trans 'Cancel' %}</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
135
templates/partials/vehicle_dropdowns.html
Normal file
135
templates/partials/vehicle_dropdowns.html
Normal file
@ -0,0 +1,135 @@
|
||||
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mt-4">
|
||||
<h2>{{ title }}</h2>
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
|
||||
<hr>
|
||||
<h4>Items</h4>
|
||||
{{ formset.management_form }}
|
||||
|
||||
<div id="formset-container">
|
||||
{% for form in formset %}
|
||||
<div class="item-form row g-3 mb-3 align-items-end">
|
||||
{{ form.id }}
|
||||
<div class="col-md-2">
|
||||
{{ form.make.label_tag }}
|
||||
{{ form.make }}
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
{{ form.model.label_tag }}
|
||||
{{ form.model }}
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
{{ form.serie.label_tag }}
|
||||
{{ form.serie }}
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
{{ form.trim.label_tag }}
|
||||
{{ form.trim }}
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
{{ form.year.label_tag }}
|
||||
{{ form.year }}
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
{{ form.color.label_tag }}
|
||||
{{ form.color }}
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
{{ form.expected_cost.label_tag }}
|
||||
{{ form.expected_cost }}
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
<button type="button" class="btn btn-danger btn-sm remove-row">Remove</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<button type="button" id="add-item" class="btn btn-secondary btn-sm mb-3">+ Add Item</button>
|
||||
|
||||
<div class="mt-3">
|
||||
<button type="submit" class="btn btn-primary">Save</button>
|
||||
<a href="{% url 'purchase_order_list' %}" class="btn btn-secondary">Cancel</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const container = document.getElementById('formset-container');
|
||||
const addBtn = document.getElementById('add-item');
|
||||
const totalForms = document.querySelector('#id_form-TOTAL_FORMS');
|
||||
const emptyForm = document.querySelector('.empty-form').cloneNode(true);
|
||||
|
||||
function updateFormIndex(formRow, index) {
|
||||
formRow.querySelectorAll('input, select').forEach(input => {
|
||||
input.name = input.name.replace('__prefix__', index);
|
||||
input.id = input.id.replace('__prefix__', index);
|
||||
});
|
||||
}
|
||||
|
||||
addBtn.addEventListener('click', function () {
|
||||
const currentCount = parseInt(totalForms.value);
|
||||
const newForm = emptyForm.cloneNode(true);
|
||||
updateFormIndex(newForm, currentCount);
|
||||
newForm.classList.remove('empty-form');
|
||||
|
||||
// Attach remove handler
|
||||
const removeBtn = newForm.querySelector('.remove-row');
|
||||
removeBtn.addEventListener('click', function () {
|
||||
newForm.remove();
|
||||
totalForms.value = parseInt(totalForms.value) - 1;
|
||||
});
|
||||
|
||||
container.appendChild(newForm);
|
||||
totalForms.value = currentCount + 1;
|
||||
});
|
||||
|
||||
document.querySelectorAll('.remove-row').forEach(btn => {
|
||||
btn.addEventListener('click', function () {
|
||||
const row = this.closest('.item-form');
|
||||
row.remove();
|
||||
totalForms.value = parseInt(totalForms.value) - 1;
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Empty form template -->
|
||||
<div class="empty-form d-none">
|
||||
<div class="item-form row g-3 mb-3 align-items-end">
|
||||
<div class="col-md-2">
|
||||
<select name="form-__prefix__-make" class="form-select make-select"></select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<select name="form-__prefix__-model" class="form-select model-select"></select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<select name="form-__prefix__-serie" class="form-select serie-select"></select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<select name="form-__prefix__-trim" class="form-select trim-select"></select>
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
<input type="number" name="form-__prefix__-year" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<input type="text" name="form-__prefix__-color" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<input type="number" step="0.01" name="form-__prefix__-expected_cost" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
<button type="button" class="btn btn-danger btn-sm remove-row">Remove</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
237
templates/purchase_orders/includes/card_po.html
Normal file
237
templates/purchase_orders/includes/card_po.html
Normal file
@ -0,0 +1,237 @@
|
||||
{% load i18n %}
|
||||
{% load django_ledger %}
|
||||
{% load widget_tweaks %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
window.showPOModal = function(title, actionUrl, buttonText) {
|
||||
const modalEl = document.getElementById('POModal');
|
||||
if (!modalEl) {
|
||||
console.error('Modal element not found');
|
||||
return;
|
||||
}
|
||||
|
||||
const modal = bootstrap.Modal.getOrCreateInstance(modalEl);
|
||||
document.getElementById('POModalTitle').textContent = title;
|
||||
|
||||
document.getElementById('POModalBody').innerHTML = `
|
||||
<div class="d-flex justify-content-center gap-3 py-3">
|
||||
<a class="btn btn-primary px-4" href="${actionUrl}">
|
||||
<i class="fas fa-check-circle me-2"></i>${buttonText}
|
||||
</a>
|
||||
<button class="btn btn-outline-secondary" data-bs-dismiss="modal">
|
||||
<i class="fas fa-times me-2"></i>Cancel
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
modal.show();
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
{% if not create_po %}
|
||||
{% if style == 'po-detail' %}
|
||||
<div class="card shadow-sm border-0 mb-4">
|
||||
<div class="card-header bg-light">
|
||||
<div class="d-flex align-items-center">
|
||||
<span class="me-3 text-primary">
|
||||
{% icon 'uil:bill' 36 %}
|
||||
</span>
|
||||
<h2 class="h3 mb-0">
|
||||
{{ po_model.po_number }}
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<h3 class="h4 mb-4">
|
||||
<span class="badge bg-{% if po_model.is_draft %}secondary{% elif po_model.is_approved %}success{% elif po_model.is_fulfilled %}primary{% else %}warning{% endif %}">
|
||||
{{ po_model.get_po_status_display }}
|
||||
</span>
|
||||
</h3>
|
||||
|
||||
{# Display PO Contract Information #}
|
||||
{% if po_model.is_contract_bound %}
|
||||
<div class="alert alert-info d-flex align-items-center mb-4">
|
||||
<i class="fas fa-file-contract me-3 fs-4"></i>
|
||||
<div>
|
||||
<h4 class="h6 mb-1">{% trans 'Contract' %}</h4>
|
||||
<p class="mb-0">{{ po_model.ce_model.estimate_number }}</p>
|
||||
</div>
|
||||
<a href="{% url 'django_ledger:customer-estimate-detail' entity_slug=view.kwargs.entity_slug ce_pk=po_model.ce_model_id %}"
|
||||
class="btn btn-sm btn-outline-info ms-auto">
|
||||
{% trans 'View Contract' %}
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="row g-3 mb-4">
|
||||
{% if po_model.is_draft %}
|
||||
<div class="col-md-6">
|
||||
<div class="card border-light h-100">
|
||||
<div class="card-body">
|
||||
<h5 class="h6 text-muted mb-2">{% trans 'Draft Date' %}</h5>
|
||||
<p class="h5">{{ po_model.date_draft|date }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card border-light h-100">
|
||||
<div class="card-body">
|
||||
<h5 class="h6 text-muted mb-2">{% trans 'Purchase Order Amount' %}</h5>
|
||||
<p class="h5">{% currency_symbol %}{{ po_model.po_amount|currency_format }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if po_model.is_review %}
|
||||
<div class="col-md-6">
|
||||
<div class="card border-light h-100">
|
||||
<div class="card-body">
|
||||
<h5 class="h6 text-muted mb-2">{% trans 'Review Date' %}</h5>
|
||||
<p class="h5">{{ po_model.date_in_review|date }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card border-light h-100">
|
||||
<div class="card-body">
|
||||
<h5 class="h6 text-muted mb-2">{% trans 'Purchase Order Amount' %}</h5>
|
||||
<p class="h5">{% currency_symbol %}{{ po_model.po_amount|currency_format }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if po_model.is_approved %}
|
||||
<div class="col-md-4">
|
||||
<div class="card border-light h-100">
|
||||
<div class="card-body">
|
||||
<h5 class="h6 text-muted mb-2">{% trans 'Approved Date' %}</h5>
|
||||
<p class="h5">{{ po_model.date_approved|date }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card border-light h-100">
|
||||
<div class="card-body">
|
||||
<h5 class="h6 text-muted mb-2">{% trans 'PO Amount' %}</h5>
|
||||
<p class="h5">{% currency_symbol %}{{ po_model.po_amount|currency_format }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card border-light h-100">
|
||||
<div class="card-body">
|
||||
<h5 class="h6 text-muted mb-2">{% trans 'Received Amount' %}</h5>
|
||||
<p class="h5 text-success">{% currency_symbol %}{{ po_model.po_amount_received|currency_format }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if po_model.is_fulfilled %}
|
||||
<div class="col-md-6">
|
||||
<div class="card border-light h-100">
|
||||
<div class="card-body">
|
||||
<h5 class="h6 text-muted mb-2">{% trans 'Fulfilled Date' %}</h5>
|
||||
<p class="h5">{{ po_model.date_fulfilled|date }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card border-light h-100">
|
||||
<div class="card-body">
|
||||
<h5 class="h6 text-muted mb-2">{% trans 'PO Amount' %}</h5>
|
||||
<div class="d-flex align-items-center">
|
||||
<p class="h5 mb-0 me-2">{% currency_symbol %}{{ po_model.po_amount|currency_format }}</p>
|
||||
<span class="badge bg-success">
|
||||
<i class="fas fa-check-circle me-1"></i>{% trans 'Fulfilled' %}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-footer bg-light">
|
||||
<div class="d-flex flex-wrap gap-2 justify-content-between">
|
||||
<a href="{% url 'purchase_order_update' entity_slug=entity_slug po_pk=po_model.pk %}"
|
||||
class="btn btn-outline-primary">
|
||||
<i class="fas fa-edit me-2"></i>{% trans 'Update' %}
|
||||
</a>
|
||||
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
{# Status Action Buttons #}
|
||||
{% if po_model.can_draft %}
|
||||
<button class="btn btn-outline-secondary"
|
||||
onclick="showPOModal('Draft PO', '{% url 'po-action-mark-as-draft' entity_slug po_model.pk %}', 'Mark As Draft')">
|
||||
<i class="fas fa-file me-2"></i>{% trans 'Mark as Draft' %}
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
{% if po_model.can_review %}
|
||||
<button class="btn btn-outline-warning"
|
||||
onclick="showPOModal('Review PO', '{% url 'po-action-mark-as-review' entity_slug po_model.pk %}', 'Mark As Review')">
|
||||
<i class="fas fa-search me-2"></i>{% trans 'Mark as Review' %}
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
{% if po_model.can_approve %}
|
||||
<button class="btn btn-outline-success"
|
||||
onclick="showPOModal('Approve PO', '{% url 'po-action-mark-as-approved' entity_slug po_model.pk %}', 'Mark As Approved')">
|
||||
<i class="fas fa-check-circle me-2"></i>{% trans 'Mark as Approved' %}
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
{% if po_model.can_fulfill %}
|
||||
<button class="btn btn-outline-primary"
|
||||
onclick="showPOModal('Fulfill PO', '{% url 'po-action-mark-as-fulfilled' entity_slug po_model.pk %}', 'Mark As Fulfilled')">
|
||||
<i class="fas fa-truck me-2"></i>{% trans 'Mark as Fulfilled' %}
|
||||
</button>
|
||||
<button class="btn btn-outline-danger"
|
||||
onclick="showPOModal('Cancel PO', '{% url 'po-action-mark-as-canceled' entity_slug po_model.pk %}', 'Mark As Cancelled')">
|
||||
<i class="fas fa-ban me-2"></i>{% trans 'Cancel' %}
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
{# Danger Action Buttons #}
|
||||
{% if po_model.can_delete %}
|
||||
<a class="btn btn-outline-danger" href="{% url 'po-delete' entity_slug po_model.pk %}">
|
||||
<i class="fas fa-ban me-2"></i>{% trans 'Delete' %}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% if po_model.can_void %}
|
||||
<button class="btn btn-outline-danger"
|
||||
onclick="djLedger.toggleModal('{{ po_model.get_mark_as_void_html_id }}')">
|
||||
<i class="fas fa-times-circle me-2"></i>{% trans 'Void' %}
|
||||
</button>
|
||||
{% modal_action_v2 bill po_model.get_mark_as_void_url po_model.get_mark_as_void_message po_model.get_mark_as_void_html_id %}
|
||||
{% endif %}
|
||||
|
||||
{% if po_model.can_cancel %}
|
||||
<button class="btn btn-outline-danger"
|
||||
onclick="djLedger.toggleModal('{{ po_model.get_mark_as_canceled_html_id }}')">
|
||||
<i class="fas fa-window-close me-2"></i>{% trans 'Cancel' %}
|
||||
</button>
|
||||
{% modal_action_v2 bill po_model.get_mark_as_canceled_url po_model.get_mark_as_canceled_message po_model.get_mark_as_canceled_html_id %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="card border-0 shadow-sm text-center py-5">
|
||||
<div class="card-body">
|
||||
<a href="{% url 'django_ledger:po-create' entity_slug=entity_slug %}" class="text-decoration-none">
|
||||
<span class="text-muted mb-3 d-inline-block">{% icon "ic:baseline-add-circle-outline" 48 %}</span>
|
||||
<h3 class="h4 text-muted">{% trans 'New Purchase Order' %}</h3>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
21
templates/purchase_orders/includes/inventory_item_form.html
Normal file
21
templates/purchase_orders/includes/inventory_item_form.html
Normal file
@ -0,0 +1,21 @@
|
||||
|
||||
<form action="{% url 'inventory_item_create' po_model.pk %}" method="post">
|
||||
{% csrf_token %}
|
||||
{% include "purchase_orders/partials/po-select.html" with name="make" target="model" data=make_data pk=po_model.pk %}
|
||||
{% include "purchase_orders/partials/po-select.html" with name="model" target="serie" data=model_data pk=po_model.pk %}
|
||||
{% include "purchase_orders/partials/po-select.html" with name="serie" target="trim" data=serie_data pk=po_model.pk %}
|
||||
{% include "purchase_orders/partials/po-select.html" with name="trim" target="none" data=trim_data pk=po_model.pk %}
|
||||
<div class="form-group">
|
||||
<label for="account">Account</label>
|
||||
<select class="form-control" name="account" id="account">
|
||||
{% for account in inventory_accounts %}
|
||||
<option value="{{ account.pk }}">{{ account }}"></option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="quantity">Quantity</label>
|
||||
<input type="number" class="form-control" id="quantity" name="quantity" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Add New Item To Inventory</button>
|
||||
</form>
|
||||
14
templates/purchase_orders/includes/mark_as.html
Normal file
14
templates/purchase_orders/includes/mark_as.html
Normal file
@ -0,0 +1,14 @@
|
||||
<!-- Modal Template (keep this in your base template) -->
|
||||
<div class="modal fade" id="POModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-md">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="POModalTitle"></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body" id="POModalBody">
|
||||
<!-- Content will be inserted here by JavaScript -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
101
templates/purchase_orders/includes/po_item_formset.html
Normal file
101
templates/purchase_orders/includes/po_item_formset.html
Normal file
@ -0,0 +1,101 @@
|
||||
{% load trans from i18n %}
|
||||
{% load django_ledger %}
|
||||
{% load widget_tweaks %}
|
||||
|
||||
<form action="{% url 'purchase_order_update_items' entity_slug=entity_slug po_pk=po_model.uuid %}"
|
||||
method="post">
|
||||
<div class="row g-3">
|
||||
<div class="col-12">
|
||||
<h1 class="display-4 mb-4">{% trans 'Purchase Order Items' %}</h1>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div class="table-responsive">
|
||||
{% csrf_token %}
|
||||
{{ itemtxs_formset.non_form_errors }}
|
||||
{{ itemtxs_formset.management_form }}
|
||||
<table class="table table-hover table-bordered">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>{% trans 'Item' %}</th>
|
||||
<th>{% trans 'Unit Cost' %}</th>
|
||||
<th>{% trans 'Quantity' %}</th>
|
||||
<th>{% trans 'Unit' %}</th>
|
||||
<th class="text-end">{% trans 'Amount' %}</th>
|
||||
<th>{% trans 'Status' %}</th>
|
||||
{% if itemtxs_formset.can_delete %}
|
||||
<th>{% trans 'Delete' %}</th>
|
||||
{% endif %}
|
||||
<th>{% trans 'Create Bill' %}</th>
|
||||
<th>{% trans 'Bill Paid?' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for f in itemtxs_formset %}
|
||||
<tr>
|
||||
<td>
|
||||
{% for hidden_field in f.hidden_fields %}
|
||||
{{ hidden_field }}
|
||||
{% endfor %}
|
||||
{{ f.item_model|add_class:"form-control" }}
|
||||
{% if f.errors %}
|
||||
<div class="text-danger small">{{ f.errors }}</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
|
||||
<td id="{{ f.instance.html_id_unit_cost }}">{{ f.po_unit_cost|add_class:"form-control" }}</td>
|
||||
<td id="{{ f.instance.html_id_quantity }}">{{ f.po_quantity|add_class:"form-control" }}</td>
|
||||
<td>{{ f.entity_unit|add_class:"form-control" }}</td>
|
||||
<td class="text-end" id="{{ f.instance.html_id_total_amount }}">
|
||||
{% currency_symbol %}{{ f.instance.po_total_amount | currency_format }}</td>
|
||||
<td>{{ f.po_item_status|add_class:"form-control" }}</td>
|
||||
{% if itemtxs_formset.can_delete %}
|
||||
<td class="text-center">
|
||||
{{ f.DELETE|add_class:"form-check-input" }}
|
||||
</td>
|
||||
{% endif %}
|
||||
<td class="text-center">
|
||||
{% if f.instance.can_create_bill %}
|
||||
{{ f.create_bill|add_class:"form-check-input" }}
|
||||
{% elif f.instance.bill_model %}
|
||||
<a class="btn btn-sm btn-outline-secondary"
|
||||
href="{% url 'django_ledger:bill-detail' entity_slug=entity_slug bill_pk=f.instance.bill_model_id %}">
|
||||
{% trans 'View Bill' %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
{% if f.instance.bill_model %}
|
||||
{% if f.instance.bill_model.is_paid %}
|
||||
<span class="text-success">{% icon 'bi:check-circle-fill' 24 %}</span>
|
||||
{% else %}
|
||||
<span class="text-danger">{% icon 'clarity:no-access-solid' 24 %}</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th class="text-end">{% trans 'Total' %}</th>
|
||||
<th class="text-end">{% currency_symbol %}{{ po_model.po_amount | currency_format }}</th>
|
||||
<th></th>
|
||||
{% if itemtxs_formset.can_delete %}
|
||||
<th></th>
|
||||
{% endif %}
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<button class="btn btn-primary">{% trans 'Save' %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
50
templates/purchase_orders/includes/po_table.html
Normal file
50
templates/purchase_orders/includes/po_table.html
Normal file
@ -0,0 +1,50 @@
|
||||
{% load django_ledger %}
|
||||
{% load i18n %}
|
||||
|
||||
<div class="table-container">
|
||||
|
||||
<table class="table is-fullwidth is-narrow is-striped is-bordered django-ledger-table-bottom-margin-75">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>PO Number</th>
|
||||
<th>Description</th>
|
||||
<th>Status Date</th>
|
||||
<th>PO Status</th>
|
||||
<th>PO Amount</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for po in po_list %}
|
||||
<tr>
|
||||
<td>{% if po.po_number %}{{ po.po_number }}{% endif %}</td>
|
||||
<td>{{ po.po_title }}</td>
|
||||
<td>{{ po.get_status_action_date }}</td>
|
||||
<td>{{ po.get_po_status_display }}</td>
|
||||
<td>{% currency_symbol %}{{ po.po_amount | currency_format }}</td>
|
||||
<td class="has-text-centered">
|
||||
<div class="dropdown is-right is-hoverable" id="bill-action-{{ po.uuid }}">
|
||||
<div class="dropdown-trigger">
|
||||
<button class="button is-small is-rounded is-outlined is-dark"
|
||||
aria-haspopup="true"
|
||||
aria-controls="dropdown-menu">
|
||||
<span>Actions</span>
|
||||
<span class="icon is-small">{% icon 'bi:arrow-down' 24 %}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="dropdown-menu" id="dropdown-menu-{{ po.uuid }}" role="menu">
|
||||
<div class="dropdown-content">
|
||||
<a href="{% url 'django_ledger:po-detail' entity_slug=entity_slug po_pk=po.uuid %}"
|
||||
class="dropdown-item has-text-success">Details</a>
|
||||
<a href="{% url 'django_ledger:po-delete' entity_slug=entity_slug po_pk=po.uuid %}"
|
||||
class="dropdown-item has-text-weight-bold has-text-danger">{% trans ' Delete' %}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
53
templates/purchase_orders/inventory_item_form.html
Normal file
53
templates/purchase_orders/inventory_item_form.html
Normal file
@ -0,0 +1,53 @@
|
||||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
{% load crispy_forms_filters %}
|
||||
{% block title %}
|
||||
{# Check if an 'object' exists in the context #}
|
||||
{% if object %}
|
||||
{% trans 'Update Inventory Item'%}
|
||||
{% else %}
|
||||
{% trans 'Add New Inventory Item'%}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="row">
|
||||
<div class="col-xl-9">
|
||||
<div class="d-sm-flex justify-content-between">
|
||||
|
||||
<h3 class="mb-3">
|
||||
{% if vendor.created %}
|
||||
<!--<i class="bi bi-pencil-square"></i>-->
|
||||
{{ _("Edit Inventory Item") }}
|
||||
{% else %}
|
||||
<!--<i class="bi bi-person-plus"></i> -->
|
||||
{{ _("Add New Inventory Item") }}
|
||||
{% endif %}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xl-9">
|
||||
|
||||
<form class="row g-3 mb-9" method="post" class="form" enctype="multipart/form-data" novalidate >
|
||||
{% csrf_token %}
|
||||
{{ redirect_field }}
|
||||
{{ form|crispy }}
|
||||
{% for error in form.errors %}
|
||||
<div class="text-danger">{{ error }}</div>
|
||||
{% endfor %}
|
||||
<div class="d-flex justify-content-start">
|
||||
<button class="btn btn-sm btn-success me-2" type="submit"><i class="fa-solid fa-floppy-disk me-1"></i>
|
||||
<!--<i class="bi bi-save"></i> -->
|
||||
{{ _("Save") }}
|
||||
</button>
|
||||
<a href="{{request.META.HTTP_REFERER}}" class="btn btn-sm btn-danger"><i class="fa-solid fa-ban me-1"></i>{% trans "Cancel" %}</a>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
13
templates/purchase_orders/partials/po-select.html
Normal file
13
templates/purchase_orders/partials/po-select.html
Normal file
@ -0,0 +1,13 @@
|
||||
<div class="form-group" id="form-{{name}}">
|
||||
<label for="{{name}}">{{name|capfirst}}</label>
|
||||
<select class="form-control"
|
||||
name="{{name}}" id="{{name}}"
|
||||
hx-get="{% url 'inventory_items_filter' po_pk=po_model.pk %}"
|
||||
hx-target="#form-{{target}}"
|
||||
hx-select="#form-{{target}}"
|
||||
hx-swap="outerHTML">
|
||||
{% for item in data %}
|
||||
<option value="{{ item.pk }}">{{ item.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
19
templates/purchase_orders/po_confirm_delete.html
Normal file
19
templates/purchase_orders/po_confirm_delete.html
Normal file
@ -0,0 +1,19 @@
|
||||
<!-- templates/purchase_orders/po_confirm_delete.html -->
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
Confirm Delete - {{ block.super }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mt-4">
|
||||
<h2>Confirm Deletion</h2>
|
||||
<p>Are you sure you want to delete the Purchase Order <strong>"{{ po.po_number }}"</strong>?</p>
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-danger">Yes, Delete</button>
|
||||
<a href="{% url 'purchase_order_detail' po.id %}" class="btn btn-secondary">Cancel</a>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
30
templates/purchase_orders/po_delete.html
Normal file
30
templates/purchase_orders/po_delete.html
Normal file
@ -0,0 +1,30 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load django_ledger %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<form action="{% url 'po-delete' entity_slug=view.kwargs.entity_slug po_pk=po_model.uuid %}"
|
||||
method="post">
|
||||
{% csrf_token %}
|
||||
<div class="card shadow">
|
||||
<div class="card-body text-center py-4">
|
||||
<h2 class="card-title h3 fw-light mb-4">Are you sure you want to delete
|
||||
Purchase Order {{ po_model.po_number }}?</h2>
|
||||
|
||||
<p class="card-text text-muted mb-4">All transactions associated with this Purchase Order will be deleted.
|
||||
If you want to void the PO instead, <a href="{% url 'django_ledger:po-detail' entity_slug=view.kwargs.entity_slug po_pk=po_model.uuid %}" class="text-decoration-none">click here</a></p>
|
||||
|
||||
<div class="d-flex justify-content-center gap-3 mt-4">
|
||||
<a href="{% url 'purchase_order_update' entity_slug=view.kwargs.entity_slug po_pk=po_model.uuid %}"
|
||||
class="btn btn-primary px-4">{% trans 'Go Back' %}</a>
|
||||
<button type="submit" class="btn btn-danger px-4">{% trans 'Delete' %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
100
templates/purchase_orders/po_detail.html
Normal file
100
templates/purchase_orders/po_detail.html
Normal file
@ -0,0 +1,100 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load trans from i18n %}
|
||||
{% load static %}
|
||||
{% load custom_filters %}
|
||||
{% load django_ledger %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid mt-4">
|
||||
<div class="row g-4">
|
||||
<!-- Left Sidebar -->
|
||||
<div class="col-lg-4">
|
||||
<div class="d-flex flex-column gap-3">
|
||||
<!-- PO Card -->
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
{% include 'purchase_orders/includes/card_po.html' with po_model=po_model entity_slug=entity_slug style='po-detail' %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PO List Button -->
|
||||
<a class="btn btn-outline-primary w-100 py-2"
|
||||
href="{% url 'purchase_order_list' %}">
|
||||
<i class="fas fa-list me-2"></i>{% trans 'PO List' %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="col-lg-8">
|
||||
<!-- Stats Cards -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<div class="row text-center">
|
||||
<div class="col-md-6 border-end">
|
||||
<div class="p-3">
|
||||
<h6 class="text-muted mb-2">{% trans 'PO Amount' %}</h6>
|
||||
<h3 class="fw-light mb-0">
|
||||
{% currency_symbol %}{{ po_model.po_amount | absolute | currency_format }}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="p-3">
|
||||
<h6 class="text-muted mb-2">{% trans 'Amount Received' %}</h6>
|
||||
<h3 class="fw-light mb-0 text-success">
|
||||
{% currency_symbol %}{{ po_model.po_amount_received | currency_format }}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PO Details -->
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h3 class="h4 fw-light mb-4">{{ po_model.po_title }}</h3>
|
||||
|
||||
<!-- PO Items Table -->
|
||||
<div class="table-responsive">
|
||||
{% po_item_table1 po_items %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% include "purchase_orders/includes/mark_as.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block customJS %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
window.showPOModal = function(title, actionUrl, buttonText) {
|
||||
const modalEl = document.getElementById('POModal');
|
||||
if (!modalEl) {
|
||||
console.error('Modal element not found');
|
||||
return;
|
||||
}
|
||||
|
||||
const modal = bootstrap.Modal.getOrCreateInstance(modalEl);
|
||||
document.getElementById('POModalTitle').textContent = title;
|
||||
|
||||
document.getElementById('POModalBody').innerHTML = `
|
||||
<div class="d-flex justify-content-center gap-3">
|
||||
<a class="btn btn-primary px-4" href="${actionUrl}">
|
||||
<i class="fas fa-check-circle me-2"></i>${buttonText}
|
||||
</a>
|
||||
<button class="btn btn-outline-secondary" data-bs-dismiss="modal">
|
||||
<i class="fas fa-times me-2"></i>Cancel
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
modal.show();
|
||||
};
|
||||
});
|
||||
</script>
|
||||
{% endblock customJS %}
|
||||
95
templates/purchase_orders/po_detail_backup.html
Normal file
95
templates/purchase_orders/po_detail_backup.html
Normal file
@ -0,0 +1,95 @@
|
||||
<!-- templates/purchase_orders/po_detail.html -->
|
||||
{% extends "base.html" %}
|
||||
{% load static i18n %}
|
||||
{% block title %}
|
||||
{{ po.po_number }} - Purchase Order - {{ block.super }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mt-4">
|
||||
<h2>Purchase Order: {{ po.po_number }}</h2>
|
||||
<p><strong>Status:</strong>
|
||||
<span class="">
|
||||
{{ po.po_status }}
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<dl class="row">
|
||||
<dt class="col-sm-4">Supplier</dt>
|
||||
<dd class="col-sm-8">{{ po.supplier.name }}</dd>
|
||||
|
||||
<dt class="col-sm-4">Created At</dt>
|
||||
<dd class="col-sm-8">{{ po.created|date:"M d, Y H:i" }}</dd>
|
||||
|
||||
<dt class="col-sm-4">Total Amount</dt>
|
||||
<dd class="col-sm-8">${{ po.total_amount|floatformat:2 }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 class="mt-4">Ordered Items</h4>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Item Number</th>
|
||||
<th>Item</th>
|
||||
<th>Unit of Measure</th>
|
||||
<th>Account</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in inventory_items %}
|
||||
<tr>
|
||||
<td>{{ item.item_number }}</td>
|
||||
<td>{{ item.name }}</td>
|
||||
<td>${{ item.uom.name }}</td>
|
||||
<td>${{ item.inventory_account }}</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="4" class="text-center">No items found.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="mt-3">
|
||||
|
||||
|
||||
<button class="btn btn-phoenix-primary" data-bs-toggle="modal" data-bs-target="#POModal"><span class="d-none d-sm-inline-block"><i class="fa-solid fa-receipt"></i> {% trans 'View Purchase Order' %}</span></button>
|
||||
<a href="{% url 'purchase_order_list' %}" class="btn btn-secondary">Back to List</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade" id="POModal" data-bs-keyboard="true" tabindex="-1" aria-labelledby="POModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header justify-content-between align-items-start gap-5 px-4 pt-4 pb-3 border-0">
|
||||
<h4 class="mb-0 me-2 text-primary"><i class="fas text-info ms-2"></i></h4>
|
||||
<button class="btn p-0 text-body-quaternary fs-6" data-bs-dismiss="modal" aria-label="Close"><span class="fas fa-times"></span></button>
|
||||
</div>
|
||||
<div class="modal-body p-4">
|
||||
<form action="{% url 'inventory_item_create' po.pk %}" method="post">
|
||||
{% csrf_token %}
|
||||
{% include "purchase_orders/partials/po-select.html" with name="make" target="model" data=make_data pk=po.pk %}
|
||||
{% include "purchase_orders/partials/po-select.html" with name="model" target="serie" data=model_data pk=po.pk %}
|
||||
{% include "purchase_orders/partials/po-select.html" with name="serie" target="trim" data=serie_data pk=po.pk %}
|
||||
{% include "purchase_orders/partials/po-select.html" with name="trim" target="none" data=trim_data pk=po.pk %}
|
||||
<div class="form-group">
|
||||
<label for="account">Account</label>
|
||||
<select class="form-control" name="account" id="account">
|
||||
{% for account in inventory_accounts %}
|
||||
<option value="{{ account.pk }}">{{ account }}"></option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="quantity">Quantity</label>
|
||||
<input type="number" class="form-control" id="quantity" name="quantity" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Add New Item To Inventory</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
53
templates/purchase_orders/po_form.html
Normal file
53
templates/purchase_orders/po_form.html
Normal file
@ -0,0 +1,53 @@
|
||||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
{% load crispy_forms_filters %}
|
||||
{% block title %}
|
||||
{# Check if an 'object' exists in the context #}
|
||||
{% if object %}
|
||||
{% trans 'Update Vendor'%}
|
||||
{% else %}
|
||||
{% trans 'Add New Vendor'%}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="row">
|
||||
<div class="col-xl-9">
|
||||
<div class="d-sm-flex justify-content-between">
|
||||
|
||||
<h3 class="mb-3">
|
||||
{% if vendor.created %}
|
||||
<!--<i class="bi bi-pencil-square"></i>-->
|
||||
{{ _("Edit Purchase Order") }}
|
||||
{% else %}
|
||||
<!--<i class="bi bi-person-plus"></i> -->
|
||||
{{ _("Add New Purchase Order") }}
|
||||
{% endif %}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xl-9">
|
||||
|
||||
<form class="row g-3 mb-9" method="post" class="form" enctype="multipart/form-data" novalidate >
|
||||
{% csrf_token %}
|
||||
{{ redirect_field }}
|
||||
{{ form|crispy }}
|
||||
{% for error in form.errors %}
|
||||
<div class="text-danger">{{ error }}</div>
|
||||
{% endfor %}
|
||||
<div class="d-flex justify-content-start">
|
||||
<button class="btn btn-sm btn-success me-2" type="submit"><i class="fa-solid fa-floppy-disk me-1"></i>
|
||||
<!--<i class="bi bi-save"></i> -->
|
||||
{{ _("Save") }}
|
||||
</button>
|
||||
<a href="{{request.META.HTTP_REFERER}}" class="btn btn-sm btn-danger"><i class="fa-solid fa-ban me-1"></i>{% trans "Cancel" %}</a>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
74
templates/purchase_orders/po_list.html
Normal file
74
templates/purchase_orders/po_list.html
Normal file
@ -0,0 +1,74 @@
|
||||
<!-- po_list.html -->
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
Purchase Orders - {{ block.super }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mt-4">
|
||||
<h2 class="mb-4">Purchase Orders</h2>
|
||||
|
||||
<!-- Success Message -->
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-success">{{ message }}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<!-- Add New PO Button -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<a href="{% url 'purchase_order_create' %}" class="btn btn-primary">
|
||||
<i class="bi bi-plus-lg"></i> Create New PO
|
||||
</a>
|
||||
</div>
|
||||
<!-- PO Table -->
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover align-middle">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th scope="col">PO Number</th>
|
||||
<th scope="col">Description</th>
|
||||
<th scope="col">Status</th>
|
||||
<th scope="col">Created At</th>
|
||||
<th scope="col">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% if purchase_orders %}
|
||||
{% for po in purchase_orders %}
|
||||
<tr>
|
||||
<td>{{ po.po_number }}</td>
|
||||
<td>{{ po.po_title }}</td>
|
||||
<td>
|
||||
<span class="">
|
||||
{{ po.po_status|capfirst }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ po.created|date:"M d, Y" }}</td>
|
||||
<td>
|
||||
<a href="{% url 'purchase_order_detail' po.pk %}" class="btn btn-sm btn-info me-1">
|
||||
View
|
||||
</a>
|
||||
{% comment %} <a href="{% url 'purchase_order_detail' po.id %}" class="btn btn-sm btn-info me-1">
|
||||
View
|
||||
</a>
|
||||
<a href="{% url 'purchase_order_update' po.id %}" class="btn btn-sm btn-warning me-1">
|
||||
Edit
|
||||
</a>
|
||||
<a href="{% url 'purchase_order_delete' po.id %}" class="btn btn-sm btn-danger">
|
||||
Delete
|
||||
</a> {% endcomment %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="6" class="text-center">No purchase orders found.</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
109
templates/purchase_orders/po_update.html
Normal file
109
templates/purchase_orders/po_update.html
Normal file
@ -0,0 +1,109 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load django_ledger %}
|
||||
{% load custom_filters %}
|
||||
{% load widget_tweaks %}
|
||||
{% load crispy_forms_tags %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid py-4">
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-12">
|
||||
{% include 'purchase_orders/includes/card_po.html' with style='po-detail' po_model=po_model entity_slug=entity_slug %}
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body">
|
||||
<form action="{% url 'purchase_order_update' entity_slug=view.kwargs.entity_slug po_pk=po_model.uuid %}"
|
||||
method="post">
|
||||
<div class="row g-3">
|
||||
<div class="col-12">
|
||||
{% csrf_token %}
|
||||
{{ form|crispy }}
|
||||
<button type="submit"
|
||||
class="btn btn-primary w-100 my-2">{% trans 'Save PO' %}
|
||||
</button>
|
||||
<a href="{% url 'purchase_order_detail' po_model.uuid %}"
|
||||
class="btn btn-dark w-100 my-2">{% trans 'Back to PO Detail' %}</a>
|
||||
<a href="{% url 'purchase_order_list' %}"
|
||||
class="btn btn-info w-100 my-2">{% trans 'PO List' %}</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if po_model.is_contract_bound %}
|
||||
<div class="col-12">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-light">
|
||||
<h3 class="h4 mb-0">
|
||||
{% trans 'Contract' %} {{ po_model.ce_model.estimate_number }}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans 'Item' %}</th>
|
||||
<th>{% trans 'Quantity' %}</th>
|
||||
<th>{% trans 'Avg Unit Price' %}</th>
|
||||
<th>{% trans 'Total Contracted Cost' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th>{% currency_symbol %}{{ ce_cost_estimate__sum | currency_format }}</th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
<tbody>
|
||||
{% for i in ce_itemtxs_agg %}
|
||||
<tr>
|
||||
<td>{{ i.item_model__name }}</td>
|
||||
<td>{{ i.ce_quantity__sum }}</td>
|
||||
<td>{% currency_symbol %}{{ i.avg_unit_cost | currency_format }}</td>
|
||||
<td>{% currency_symbol %}{{ i.ce_cost_estimate__sum | currency_format }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="col-12">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body">
|
||||
{% po_item_formset_table po_model itemtxs_formset %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include "purchase_orders/includes/mark_as.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block customJS %}
|
||||
<script>
|
||||
function showPOModal(title, actionUrl, buttonText) {
|
||||
const modal = new bootstrap.Modal(document.getElementById('POModal'));
|
||||
document.getElementById('POModalTitle').innerText = title;
|
||||
|
||||
// Set the modal body content
|
||||
document.getElementById('POModalBody').innerHTML = `
|
||||
<a class="btn btn-primary" href="${actionUrl}">
|
||||
${buttonText}
|
||||
</a>
|
||||
`;
|
||||
modal.show();
|
||||
}
|
||||
</script>
|
||||
{% endblock customJS %}
|
||||
49
templates/purchase_orders/tags/po_item_table.html
Normal file
49
templates/purchase_orders/tags/po_item_table.html
Normal file
@ -0,0 +1,49 @@
|
||||
{% load i18n %}
|
||||
{% load django_ledger %}
|
||||
|
||||
<div class="table-container">
|
||||
<table class="table is-fullwidth is-striped is-narrow is-bordered">
|
||||
<thead>
|
||||
<tr class="has-text-centered">
|
||||
<th>{% trans 'Item' %}</th>
|
||||
<th>{% trans 'Unit Cost' %}</th>
|
||||
<th>{% trans 'PO Qty' %}</th>
|
||||
<th>{% trans 'Amount' %}</th>
|
||||
<th>{% trans 'Status' %}</th>
|
||||
<th>{% trans 'Billed' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in po_item_list %}
|
||||
<tr>
|
||||
<td>{{ item.item_model }}</td>
|
||||
<td class="has-text-centered">{{ item.po_unit_cost }}</td>
|
||||
<td class="has-text-centered">{{ item.po_quantity }}</td>
|
||||
<td class="{% if item.is_cancelled %}djl-is-strikethrough{% endif %} has-text-centered">
|
||||
{% currency_symbol %}{{ item.po_total_amount | currency_format }}</td>
|
||||
<td class="has-text-weight-bold has-text-centered {% if item.is_cancelled %}has-text-danger{% endif %}">
|
||||
{% if item.po_item_status %}
|
||||
{{ item.get_po_item_status_display }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="has-text-centered">{% if item.bill_model_id %}
|
||||
<a class="is-small is-info button"
|
||||
href="{% url 'django_ledger:bill-detail' entity_slug=entity_slug bill_pk=item.bill_model_id %}">View
|
||||
Bill</a>{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td class="has-text-right">{% trans 'Total PO Amount' %}</td>
|
||||
<td class="has-text-weight-bold has-text-centered">
|
||||
{% currency_symbol %}{{ po_model.po_amount | currency_format }}</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
Loading…
x
Reference in New Issue
Block a user