update invoice

This commit is contained in:
ismail 2025-07-08 17:58:34 +03:00
parent 4af455510f
commit b47231ed2a
8 changed files with 152 additions and 47 deletions

View File

@ -2002,7 +2002,13 @@ class Opportunity(models.Model):
return self.lead.get_calls()
def get_schedules(self):
# qs = Schedule.objects.filter(
# dealer=self.dealer,
# content_type__model__in=["lead"], object_id=self.object.id,
# scheduled_by=self.request.user
# )
return (
self.lead.get_all_schedules()
.filter(scheduled_at__gt=timezone.now())
.order_by("scheduled_at")
@ -2323,6 +2329,14 @@ class Payment(models.Model):
max_length=100, null=True, blank=True, verbose_name=_("reference number")
)
payment_date = models.DateField(auto_now_add=True, verbose_name=_("date"))
invoice = models.ForeignKey(
InvoiceModel,
on_delete=models.CASCADE,
related_name="payments",
verbose_name=_("invoice"),
null=True,
blank=True,
)
class Meta:
verbose_name = _("payment")
@ -2646,7 +2660,7 @@ class CustomGroup(models.Model):
allowed_models=[],
other_perms=[
"view_purchaseordermodel",
]
)
######################################
@ -2724,7 +2738,7 @@ class CustomGroup(models.Model):
"itemmodel",
"invoicemodel",
"vendormodel",
"journalentrymodel",
"purchaseordermodel",
"estimatemodel",
@ -2982,4 +2996,50 @@ class ExtraInfo(models.Model):
verbose_name_plural = "Extra Info"
def __str__(self):
return f"ExtraInfo for {self.content_object} ({self.content_type})"
return f"ExtraInfo for {self.content_object} ({self.content_type})"
@classmethod
def get_sale_orders(cls, staff=None, is_dealer=False):
if not staff and not is_dealer:
return []
content_type = ContentType.objects.get_for_model(EstimateModel)
related_content_type = ContentType.objects.get_for_model(Staff)
if is_dealer:
qs = cls.objects.filter(
content_type=content_type,
related_content_type=related_content_type,
related_object_id__isnull=False
)
else:
qs = cls.objects.filter(
content_type=content_type,
related_content_type=related_content_type,
related_object_id=staff.pk
)
return [x.content_object.sale_orders.first() for x in qs if x.content_object.sale_orders.first()]
@classmethod
def get_invoices(cls, staff=None, is_dealer=False):
if not staff and not is_dealer:
return []
content_type = ContentType.objects.get_for_model(EstimateModel)
related_content_type = ContentType.objects.get_for_model(Staff)
if is_dealer:
qs = cls.objects.filter(
content_type=content_type,
related_content_type=related_content_type,
related_object_id__isnull=False
)
else:
qs = cls.objects.filter(
content_type=content_type,
related_content_type=related_content_type,
related_object_id=staff.pk
)
print(qs[0].content_object.invoicemodel_set.first())
return [x.content_object.invoicemodel_set.first() for x in qs if x.content_object.invoicemodel_set.first()]

View File

@ -642,3 +642,6 @@ class BaseBillActionView(LoginRequiredMixin,PermissionRequiredMixin, RedirectVie
level=messages.ERROR,
extra_tags='is-danger')
return response

View File

@ -226,8 +226,8 @@ def reserve_car(car, request):
car.save()
# --- Logging for Success ---
logger.info(
f"Car {car.id} ('{car.make} {car.model}') reserved successfully "
f"by user {request.user.id} ('{request.user.username}'). "
f"Car {car.pk} ('{car.id_car_make} {car.id_car_model}') reserved successfully "
f"by user {request.user}. "
f"Reserved until: {reserved_until}."
)
@ -235,8 +235,8 @@ def reserve_car(car, request):
except Exception as e:
# --- Logging for Error ---
logger.error(
f"Error reserving car {car.id} ('{car.make} {car.model}') "
f"for user {request.user.id} ('{request.user.username}'). "
f"Error reserving car {car.pk} ('{car.id_car_make} {car.id_car_model}') "
f"for user {request.user} . "
f"Error: {e}",
exc_info=True
)
@ -473,7 +473,7 @@ def set_invoice_payment(dealer, entity, invoice, amount, payment_method):
calculator = CarFinanceCalculator(invoice)
finance_data = calculator.get_finance_data()
handle_account_process(invoice, amount, finance_data)
# handle_account_process(invoice, amount, finance_data)
invoice.make_payment(amount)
invoice.save()

View File

@ -4210,13 +4210,12 @@ def sales_list_view(request, dealer_slug):
staff = getattr(request.user.staffmember, "staff", None)
qs = []
try:
if dealer:
if request.is_dealer:
qs = models.ExtraInfo.get_sale_orders(staff=staff,is_dealer=True)
else:
elif request.is_staff:
qs = models.ExtraInfo.get_sale_orders(staff=staff)
except Exception as e:
print(e)
print(qs)
# sale_orders = models.SaleOrder.objects.filter(
# dealer=dealer,
# )
@ -4289,19 +4288,19 @@ class EstimateListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"])
staff = getattr(self.request.user.staffmember, "staff", None)
dealer = getattr(self.request.user, "dealer", None)
if dealer:
if self.request.is_dealer:
qs = models.ExtraInfo.objects.filter(
content_type=ContentType.objects.get_for_model(EstimateModel),
related_content_type=ContentType.objects.get_for_model(models.Staff),
)
else:
elif self.request.is_staff:
qs = models.ExtraInfo.objects.filter(
content_type=ContentType.objects.get_for_model(EstimateModel),
related_content_type=ContentType.objects.get_for_model(models.Staff),
related_object_id=staff.pk if staff else self.request.user.pk,
related_object_id=staff.pk,
)
context["staff_estimates"] = qs
return context
@ -4450,17 +4449,17 @@ def create_estimate(request, dealer_slug, slug=None):
}
for item in items_txs
}
else:
item = entity.get_items_all().filter(pk=items).first()
instance = models.Car.objects.get(vin=item.name)
estimate_itemtxs = {
item.item_number: {
"unit_cost": instance.finances.cost_price,
"unit_revenue": instance.finances.selling_price,
"quantity": Decimal(quantities),
"total_amount": instance.finances.total_vat * int(quantities),
}
}
# else:
# item = entity.get_items_all().filter(pk=items).first()
# instance = models.Car.objects.get(vin=item.name)
# estimate_itemtxs = {
# item.item_number: {
# "unit_cost": instance.finances.cost_price,
# "unit_revenue": instance.finances.selling_price,
# "quantity": Decimal(quantities),
# "total_amount": instance.finances.total_vat * int(quantities),
# }
# }
try:
estimate.migrate_itemtxs(
@ -4478,16 +4477,14 @@ def create_estimate(request, dealer_slug, slug=None):
)
if isinstance(items, list):
for item in estimate_itemtxs.keys():
item_instance = ItemModel.objects.filter(item_number=item).first()
instance = models.Car.objects.get(vin=item_instance.name)
# reserve_car(instance, request) #TODO
item_instance = entity.get_items_all().filter(item_number=item).first()
else:
item_instance = ItemModel.objects.filter(
additioinal_info__car_info__hash=items
).first()
instance = models.Car.objects.get(hash=item)
# reserve_car(instance, request) #TODO
# else:
# item_instance = ItemModel.objects.filter(
# additioinal_info__car_info__hash=items
# ).first()
# instance = models.Car.objects.get(hash=item)
# reserve_car(instance, request) #TODO
opportunity_id = data.get("opportunity_id")
if opportunity_id != "None":
@ -4518,7 +4515,8 @@ def create_estimate(request, dealer_slug, slug=None):
"url": f"{url}",
}
)
#######################################
##GET
form = forms.EstimateModelCreateForm()
form.fields["customer"].queryset = models.Customer.objects.filter(
dealer=dealer, active=True
@ -4860,6 +4858,9 @@ def estimate_mark_as(request, dealer_slug, pk):
)
estimate.mark_as_approved()
estimate.save()
#Reserve The Car
car = estimate.get_itemtxs_data()[0].first().item_model.car
reserve_car(car, request)
messages.success(request, _("Quotation approved successfully"))
return redirect(
"estimate_list", dealer_slug=dealer.slug
@ -4934,8 +4935,20 @@ class InvoiceListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
def get_queryset(self):
dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"])
entity = dealer.entity
staff = getattr(self.request.user.staffmember, "staff", None)
qs = []
try:
if self.request.is_dealer:
qs = models.ExtraInfo.get_invoices(staff=staff,is_dealer=True)
elif self.request.is_staff:
qs = models.ExtraInfo.get_invoices(staff=staff)
except Exception as e:
print(e)
query = self.request.GET.get("q")
invoices = dealer.entity.get_invoices()
invoices = qs
# invoices = dealer.entity.get_invoices()
return apply_search_filters(invoices, query)
@ -5624,7 +5637,8 @@ class LeadDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
dealer=dealer,
content_type__model="lead", object_id=self.object.id,
scheduled_by=self.request.user
)
).order_by("-created_at")
context["status_history"] = models.LeadStatusHistory.objects.filter(
lead=self.object
)
@ -6193,6 +6207,14 @@ def schedule_event(request, dealer_slug,content_type,slug):
)
instance.save()
models.Activity.objects.create(
dealer=dealer,
content_object=obj,
notes=instance.notes,
created_by=request.user,
activity_type=instance.scheduled_type,
)
# --- Logging for successful AppointmentRequest and Appointment creation ---
logger.info(
f"User {user_username} successfully scheduled {content_type} ID: {obj.pk} ('{obj.slug}'). "
@ -6551,6 +6573,7 @@ class OpportunityDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailV
permission_required = ["inventory.view_opportunity"]
def get_context_data(self, **kwargs):
dealer = get_object_or_404(models.Dealer,slug=self.kwargs.get("dealer_slug"))
context = super().get_context_data(**kwargs)
form = forms.OpportunityStatusForm()
url = reverse("opportunity_update_status", kwargs={"dealer_slug": self.kwargs["dealer_slug"], "slug": self.object.slug})
@ -6596,11 +6619,24 @@ class OpportunityDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailV
context["tasks"] = models.Tasks.objects.filter(
content_type__model="opportunity", object_id=self.object.id
)
qs = models.Schedule.objects.filter(
dealer=dealer,
content_type__model="lead", object_id=self.object.lead.id,
scheduled_by=self.request.user
)
context["schedules"] = qs | models.Schedule.objects.filter(
dealer=dealer,
content_type__model="opportunity", object_id=self.object.id,
scheduled_by=self.request.user
)
context["schedules"] = context["schedules"].order_by("-created_at")
context["upcoming_events"] = {
"schedules": self.object.lead.get_all_schedules().filter(
"schedules": qs.filter(
scheduled_at__gt=timezone.now()
),
)[:5],
}
context["schedule_form"] = forms.ScheduleForm()
return context
@ -7504,6 +7540,7 @@ class OrderListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
def get_queryset(self):
dealer = get_user_type(self.request)
qs = super().get_queryset()
print(qs)
return qs.filter(estimate__entity=dealer.entity)
@ -8678,7 +8715,7 @@ class LedgerModelCreateView(LedgerModelCreateViewBase):
)
def form_valid(self, form):
dealer = get_user_type(self.request)
dealer = get_user_type(self.request)
form.field["entity"] = dealer.entity
return super().form_valid(form)

View File

@ -86,7 +86,7 @@
<div class="row g-3">
<div class="col-12 overflow-auto" style="max-height: 200px;">
<ul class="list-group list-group-flush">
{% for event in opportunity.get_schedules %}
{% for event in schedules %}
<li class="list-group-item d-flex justify-content-between align-items-center">
<div class="d-flex align-items-center">
<span class="badge rounded-pill bg-phoenix-primary text-primary me-2 fs-9">{{ event.scheduled_type|capfirst }}</span>
@ -348,7 +348,7 @@
<div class="mb-1 d-flex justify-content-between align-items-center">
<h3 class="mb-0" id="scrollspyEmails">{{ _("Tasks") }}</h3>
{% if perms.inventory.change_opportunity%}
<button class="btn btn-phoenix-primary btn-sm" type="button" data-bs-toggle="modal" data-bs-target="#taskModal"><span class="fas fa-plus me-1"></span>{{ _("Add Task") }}</button>
<button class="btn btn-phoenix-primary btn-sm" type="button" data-bs-toggle="modal" data-bs-target="#scheduleModal"><span class="fas fa-plus me-1"></span>{{ _("Add Task") }}</button>
{% endif %}
</div>
<div>
@ -364,13 +364,14 @@
</div>
</th>
<th class="sort white-space-nowrap align-middle pe-3 ps-0 text-uppercase" scope="col" data-sort="subject" style="width:31%; min-width:350px">Title</th>
<th class="sort white-space-nowrap align-middle pe-3 ps-0 text-uppercase" scope="col" data-sort="subject" style="width:31%; min-width:350px">Notes</th>
<th class="sort align-middle pe-3 text-uppercase" scope="col" data-sort="sent" style="width:15%; min-width:130px">Assigned to</th>
<th class="sort align-middle text-start text-uppercase" scope="col" data-sort="date" style="min-width:165px">Due Date</th>
<th class="sort align-middle text-start text-uppercase" scope="col" data-sort="date" style="min-width:165px">Completed</th>
</tr>
</thead>
<tbody class="list" id="all-tasks-table-body">
{% for task in opportunity.get_tasks %}
{% for task in schedules %}
{% include "partials/task.html" %}
{% endfor %}
</tbody>
@ -697,7 +698,7 @@
</div>
{%if perms.inventory.change_opportunity%}
<div class="col-auto">
<button class="btn btn-phoenix-primary btn-sm" type="button" data-bs-toggle="modal" data-bs-target="#activityModal"><span class="fas fa-plus me-1"></span>{{ _("Add Activity") }}</button>
</div>
{% endif %}
</div>
@ -752,4 +753,6 @@
{% include "components/task_modal.html" with content_type="opportunity" slug=opportunity.slug %}
<!-- note Modal -->
{% include "components/note_modal.html" with content_type="opportunity" slug=opportunity.slug %}
<!-- schedule Modal -->
{% include "components/schedule_modal.html" with content_type="opportunity" slug=opportunity.slug %}
{% endblock %}

View File

@ -470,7 +470,7 @@
</li>
{% if request.is_staff %}
<li class="nav-item">
<a class="nav-link px-3 d-block" href="{% url 'appointment:get_user_appointments' %}"> <span class="me-2 text-body align-bottom" data-feather="calendar"></span>{{ _("Calendar") }}</a>
<a class="nav-link px-3 d-block" href="{% url 'appointment:get_user_appointments' %}"> <span class="me-2 text-body align-bottom" data-feather="calendar"></span>{{ _("My Calendar") }}</a>
</li>
{% endif %}
<!--<li class="nav-item"><a class="nav-link px-3 d-block" href=""> Language</a></li>-->

View File

@ -2,12 +2,13 @@
<tr id="task-{{task.pk}}" class="hover-actions-trigger btn-reveal-trigger position-static {% if task.completed %}completed-task{% endif %}">
<td class="fs-9 align-middle px-0 py-3">
<div class="form-check mb-0 fs-8">
<input class="form-check-input" {% if task.completed %}checked{% endif %} type="checkbox" hx-post="{% url 'update_schedule' request.dealer.slug task.pk %}" hx-trigger="change" hx-swap="outerHTML" hx-target="#task-{{task.pk}}" />
<input class="form-check-input" {% if task.completed %}checked{% endif %} type="checkbox" hx-post="{% url 'update_schedule' request.dealer.slug task.pk %}" hx-trigger="change" hx-swap="outerHTML" hx-target="#task-{{task.pk}}" />
</div>
</td>
<td class="subject order align-middle white-space-nowrap py-2 ps-0"><a class="fw-semibold text-primary" href="">{{task.purpose|capfirst}}</a>
<div class="fs-10 d-block">{{task.scheduled_type|capfirst}}</div>
</td>
<td class="sent align-middle white-space-nowrap text-start fw-thin text-body-tertiary py-2">{{task.notes}}</td>
<td class="sent align-middle white-space-nowrap text-start fw-bold text-body-tertiary py-2">{{task.scheduled_by}}</td>
<td class="date align-middle white-space-nowrap text-body py-2">{{task.created_at|naturalday|capfirst}}</td>
<td class="date align-middle white-space-nowrap text-body py-2">

View File

@ -166,6 +166,7 @@
<span>{{item.make}} {{item.model}} {{item.serie}} {{item.trim}} {{item.color_name}}</span>
<div class="color-box" style="background-color: rgb({{ item.exterior_color }});"></div>
<div class="color-box" style="background-color: rgb({{ item.interior_color }});"></div>
<span style="color:gray;">({{item.hash_count}} in stock)</span>
</div>
{% endfor %}
</div>