update invoice
This commit is contained in:
parent
4af455510f
commit
b47231ed2a
@ -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()]
|
||||
|
||||
@ -642,3 +642,6 @@ class BaseBillActionView(LoginRequiredMixin,PermissionRequiredMixin, RedirectVie
|
||||
level=messages.ERROR,
|
||||
extra_tags='is-danger')
|
||||
return response
|
||||
|
||||
|
||||
|
||||
|
||||
@ -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()
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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 %}
|
||||
@ -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>-->
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user