From 0a3d8d3871218497e6c3e4137b051805cecc5b0a Mon Sep 17 00:00:00 2001 From: ismail Date: Tue, 17 Jun 2025 14:44:28 +0300 Subject: [PATCH] add bulk insert + po item insert --- inventory/management/commands/check_vin.py | 26 +++++ inventory/urls.py | 4 +- inventory/views.py | 85 ++++++++++++---- static/sample/cars_sample.csv | 2 + templates/csv_upload.html | 97 +++++++++---------- .../car_inventory_item_form.html | 6 +- .../purchase_orders/includes/card_po.html | 38 +------- templates/purchase_orders/po_list.html | 20 +++- templates/purchase_orders/po_upload_cars.html | 36 +++++++ templates/sales/orders/order_details.html | 57 +++++++++++ 10 files changed, 254 insertions(+), 117 deletions(-) create mode 100644 inventory/management/commands/check_vin.py create mode 100644 static/sample/cars_sample.csv create mode 100644 templates/purchase_orders/po_upload_cars.html diff --git a/inventory/management/commands/check_vin.py b/inventory/management/commands/check_vin.py new file mode 100644 index 00000000..43d44d93 --- /dev/null +++ b/inventory/management/commands/check_vin.py @@ -0,0 +1,26 @@ +from django.core.management.base import BaseCommand + +from inventory.services import get_model,get_make,decodevin + +class Command(BaseCommand): + help = 'Seed the Customer model with 20 records' + + def handle(self, *args, **kwargs): + # vin,description = self.generate_vin() + result = decodevin("1HGCM82633A123456") + self.stdout.write(self.style.SUCCESS('####################################################################################################')) + self.stdout.write(self.style.SUCCESS('####################################################################################################')) + # self.stdout.write(self.style.SUCCESS(f'Generated VIN: {vin}')) + # self.stdout.write(self.style.SUCCESS(f'Description: {description}')) + self.stdout.write(self.style.SUCCESS('####################################################################################################')) + self.stdout.write(self.style.SUCCESS('####################################################################################################')) + self.stdout.write(self.style.SUCCESS(f'Decoded VIN: {result}')) + make,model,year_model = result.values() + self.stdout.write(self.style.SUCCESS(f'VIN:"1HGCM82633A123456" - Make {make} - Model {model} - Model Year {year_model}')) + make = get_make(make) + model = get_model(model,make) + + self.stdout.write(self.style.SUCCESS(f'Make: {make} - Model: {model} - Year: {year_model}')) + self.stdout.write(self.style.SUCCESS('####################################################################################################')) + self.stdout.write(self.style.SUCCESS('####################################################################################################')) + diff --git a/inventory/urls.py b/inventory/urls.py index 197cbcfc..0255b734 100644 --- a/inventory/urls.py +++ b/inventory/urls.py @@ -255,7 +255,7 @@ urlpatterns = [ ), # Car URLs path('cars/upload_cars/', views.upload_cars, name='upload_cars'), - path('cars//upload_cars/', views.upload_cars, name='upload_cars'), + path('cars//upload_cars/', views.upload_cars, name='upload_cars'), path("cars/add/", views.CarCreateView.as_view(), name="car_add"), path("cars/inventory/", views.CarInventory.as_view(), name="car_inventory_all"), path( @@ -874,7 +874,7 @@ path( path('purchase_orders//delete//', views.PurchaseOrderModelDeleteView.as_view(), name='po-delete'), - + path('purchase_orders///upload/',view=views.view_items_inventory,name='view_items_inventory'), # Actions.... path('/action//mark-as-draft/', views.PurchaseOrderMarkAsDraftView.as_view(), diff --git a/inventory/views.py b/inventory/views.py index 4320ae97..7b5a06db 100644 --- a/inventory/views.py +++ b/inventory/views.py @@ -8858,7 +8858,7 @@ def InventoryItemCreateView(request): 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} - {year} - {exterior.name} - {interior.name}" + inventory_name = f"{make_name.name} || {model_name.name} || {serie_name.name} || {trim_name.name} || {year} || {exterior.name} || {interior.name}" uom = entity.get_uom_all().get(name='Unit') entity.create_item_inventory( name=inventory_name, @@ -9187,29 +9187,56 @@ class BillModelActionForceMigrateView(BaseBillActionView): ############################################################### ############################################################### -def upload_cars(request,po_pk=None): +def view_items_inventory(request,entity_slug,po_pk): dealer = get_user_type(request) + po = PurchaseOrderModel.objects.get(pk=po_pk) + items = po.get_itemtxs_data()[0] + return render(request,'purchase_orders/po_upload_cars.html',{'po':po,"items":items}) + +def upload_cars(request,pk=None): + item = None + dealer = get_user_type(request) + response = redirect('upload_cars') + if pk: + item = get_object_or_404(ItemTransactionModel, pk=pk) + response = redirect('upload_cars', pk=pk) + if item.item_model.additional_info["uploaded"]: + messages.add_message(request, messages.ERROR, 'Item already uploaded.') + return redirect('view_items_inventory', entity_slug=dealer.slug, po_pk=item.po_model.pk) if request.method == 'POST': csv_file = request.FILES.get('csv_file') - year = request.POST.get("year") - receiving_date = datetime.strptime(request.POST.get("receiving_date"), "%Y-%m-%d") - receiving_date = timezone.make_aware(receiving_date) try: - make = models.CarMake.objects.get(pk=request.POST.get("make")) - model = models.CarModel.objects.get(pk=request.POST.get("model")) - serie = models.CarSerie.objects.get(pk=request.POST.get("serie")) - trim = models.CarTrim.objects.get(pk=request.POST.get("trim")) - vendor = models.Vendor.objects.get(pk=request.POST.get("vendor")) - exterior = models.ExteriorColors.objects.get(pk=request.POST.get("exterior")) - interior = models.InteriorColors.objects.get(pk=request.POST.get("interior")) + if item: + item = ItemTransactionModel.objects.get(pk=pk) + data = [x.strip() for x in item.item_model.name.split("||")] + make = models.CarMake.objects.get(name=data[0]) + model = make.carmodel_set.get(name=data[1]) + trim = models.CarTrim.objects.filter(name=data[3],id_car_serie__id_car_model=model.id_car_model).first() + serie = trim.id_car_serie + year = data[4] + exterior = models.ExteriorColors.objects.get(name=data[5]) + interior = models.InteriorColors.objects.get(name=data[6]) + receiving_date = timezone.now() + vendor_model = item.bill_model.vendor + vendor = models.Vendor.objects.get(vendor_model=vendor_model) + else: + make = models.CarMake.objects.get(pk=request.POST.get("make")) + model = models.CarModel.objects.get(pk=request.POST.get("model")) + serie = models.CarSerie.objects.get(pk=request.POST.get("serie")) + trim = models.CarTrim.objects.get(pk=request.POST.get("trim")) + exterior = models.ExteriorColors.objects.get(pk=request.POST.get("exterior")) + interior = models.InteriorColors.objects.get(pk=request.POST.get("interior")) + year = request.POST.get("year") + receiving_date = datetime.strptime(request.POST.get("receiving_date"), "%Y-%m-%d") + vendor = models.Vendor.objects.get(pk=request.POST.get("vendor")) + + except Exception as e: messages.error(request, f"Error processing CSV: {str(e)}") - if po_pk: - return redirect('upload_cars',po_pk=po_pk) - else: - return redirect('upload_cars') + return response + if not csv_file.name.endswith('.csv'): messages.error(request, "Please upload a CSV file") return redirect('upload_cars') @@ -9218,8 +9245,21 @@ def upload_cars(request,po_pk=None): file_content = csv_file.read().decode('utf-8') csv_data = io.StringIO(file_content) reader = csv.DictReader(csv_data) + data = [x for x in reader] + for row in data: + if result := decodevin(row['vin']): + if models.Car.objects.filter(vin=row['vin']).exists(): + messages.error(request, f"vin {row['vin']} already exists") + return response + manufacturer_name, model_name, year_model = result.values() + car_make = get_make(manufacturer_name) + car_model = get_model(model_name, car_make) + if not all([car_make, car_model]) or (make.pk != car_make.pk) or (model.pk != car_model.pk) or (int(year) != int(year_model)): + messages.error(request, f"invalid data at vin {row['vin']}, Please upload a valid CSV file") + return response + cars_created = 0 - for row in reader: + for row in data: car = models.Car.objects.create( dealer=dealer, vin=row['vin'], @@ -9227,19 +9267,24 @@ def upload_cars(request,po_pk=None): id_car_model=model, id_car_serie=serie, id_car_trim=trim, - year=year, + year=int(year), vendor=vendor, receiving_date=receiving_date, ) car.add_colors(exterior=exterior, interior=interior) cars_created += 1 + if item: + item.item_model.additional_info["uploaded"] = True + item.item_model.save() messages.success(request, f"Successfully imported {cars_created} cars") - return redirect('car_list') # redirect to your car list view + return response except Exception as e: messages.error(request, f"Error processing CSV: {str(e)}") form = forms.CSVUploadForm() - return render(request, 'csv_upload.html',{"make_data":models.CarMake.objects.all(),"form":form}) + form.fields["vendor"].queryset = dealer.vendors.all() + + return render(request, 'csv_upload.html',{"make_data":models.CarMake.objects.all(),"form":form,"item":item}) ############################################################### ############################################################### \ No newline at end of file diff --git a/static/sample/cars_sample.csv b/static/sample/cars_sample.csv new file mode 100644 index 00000000..04bda56e --- /dev/null +++ b/static/sample/cars_sample.csv @@ -0,0 +1,2 @@ +vin, +1HGCM82633A123456, diff --git a/templates/csv_upload.html b/templates/csv_upload.html index 89d6206f..cbd1e83e 100644 --- a/templates/csv_upload.html +++ b/templates/csv_upload.html @@ -74,18 +74,23 @@ {% block content %}

Upload Cars CSV

+ {% if messages %} {% for message in messages %} -
- {{ message }} -
+ {% endfor %} {% endif %}
{% csrf_token %} - + {% if not item %}
{% include "purchase_orders/partials/po-select.html" with name="make" target="model" data=make_data pk=po_model.pk %} @@ -117,70 +122,56 @@
+

{% trans 'Exterior Colors' %}

- {% for color in form.fields.exterior.queryset %} -
- -
- {% endfor %} + {% for color in form.fields.exterior.queryset %} +
+ +
+ {% endfor %}
- +
+

{% trans 'Interior Colors' %}

- {% for color in form.fields.interior.queryset %} -
- -
- {% endfor %} + {% for color in form.fields.interior.queryset %} +
+ +
+ {% endfor %}
+
+{% endif %} + {% if item %} +

List of Items

+
    +
  • + {{ item.item_model }} +
  • +
+ {% endif %}
- CSV should include columns: vin, make, model, year (required) + CSV should include columns: vin
- - Cancel + Cancel - -
-

CSV Format Example

- - - - - - - - - - - - - - - - - -
vinmakemodelyear
1HGCM82633A123456HondaAccord2023
- - Download Sample CSV - -
{% endblock %} \ No newline at end of file diff --git a/templates/purchase_orders/car_inventory_item_form.html b/templates/purchase_orders/car_inventory_item_form.html index 8b531be9..95a5f936 100644 --- a/templates/purchase_orders/car_inventory_item_form.html +++ b/templates/purchase_orders/car_inventory_item_form.html @@ -106,9 +106,8 @@ {% endfor %}
-<<<<<<< HEAD - -======= + +

{% trans 'Exterior Colors' %}

@@ -143,6 +142,5 @@
->>>>>>> 90fea4d25623ba4dd0f6fd2390e23b40857b6dff {% endblock content %} \ No newline at end of file diff --git a/templates/purchase_orders/includes/card_po.html b/templates/purchase_orders/includes/card_po.html index ff27515c..e1e0f134 100644 --- a/templates/purchase_orders/includes/card_po.html +++ b/templates/purchase_orders/includes/card_po.html @@ -58,13 +58,11 @@ document.addEventListener('DOMContentLoaded', function() {

{% trans 'Contract' %}

{{ po_model.ce_model.estimate_number }}

-<<<<<<< HEAD - -======= + + ->>>>>>> 90fea4d25623ba4dd0f6fd2390e23b40857b6dff + {% trans 'View Contract' %} @@ -197,35 +195,7 @@ document.addEventListener('DOMContentLoaded', function() { onclick="showPOModal('Fulfill PO', '{% url 'po-action-mark-as-fulfilled' entity_slug po_model.pk %}', 'Mark As Fulfilled')"> {% trans 'Mark as Fulfilled' %} -<<<<<<< HEAD - - {% endif %} - {# Danger Action Buttons #} - {% if po_model.can_delete %} - - {% trans 'Delete' %} - - {% endif %} - - {% if po_model.can_void %} - - {% 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 %} - - {% 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 %} {# Danger Action Buttons #} @@ -248,7 +218,7 @@ document.addEventListener('DOMContentLoaded', function() { onclick="showPOModal('Cancel PO', '{% url 'po-action-mark-as-canceled' entity_slug po_model.pk %}', 'Mark As Cancelled')"> {% trans 'Cancel' %} ->>>>>>> 90fea4d25623ba4dd0f6fd2390e23b40857b6dff + {% endif %} diff --git a/templates/purchase_orders/po_list.html b/templates/purchase_orders/po_list.html index a4e8d374..fba40c74 100644 --- a/templates/purchase_orders/po_list.html +++ b/templates/purchase_orders/po_list.html @@ -41,22 +41,34 @@ {% if purchase_orders %} {% for po in purchase_orders %} - {{ po.po_number }} {{ po.po_title }} + {% if po.po_status == 'draft' %} + + {% elif po.po_status == 'in_review' %} + + {% elif po.po_status == 'paid' %} + + {% elif po.po_status == 'canceled' %} + + {% elif po.po_status == 'fulfilled' %} + + {% elif po.po_status == 'approved' %} + + {% endif %} {{ po.po_status|capfirst }} {{ po.created|date:"M d, Y" }} - + diff --git a/templates/purchase_orders/po_upload_cars.html b/templates/purchase_orders/po_upload_cars.html new file mode 100644 index 00000000..95d3b2e6 --- /dev/null +++ b/templates/purchase_orders/po_upload_cars.html @@ -0,0 +1,36 @@ +{% extends "base.html" %} + +{% block content %} +

{{po.po_number}}

+

{{po.po_status|capfirst}}

+
+ + + + + + + + + + + {% for item in items %} + + + + + + + {% endfor %} + +
NameQuatnityUnit CostIs Data Uploaded ?
{{ item.item_model }}{{ item.po_quantity }}{{ item.po_unit_cost }} + {% if item.item_model.additional_info.uploaded %} + + {% else %} + + Upload Data + + {% endif %} +
+
+{% endblock content %} \ No newline at end of file diff --git a/templates/sales/orders/order_details.html b/templates/sales/orders/order_details.html index ef0672d7..1cabeab3 100644 --- a/templates/sales/orders/order_details.html +++ b/templates/sales/orders/order_details.html @@ -81,6 +81,10 @@ {{ _("Sale Order")}} #{{ saleorder.formatted_order_id }}
+<<<<<<< HEAD +======= + +>>>>>>> e9e2fd3 (add bulk insert + po item insert) @@ -271,9 +275,17 @@
+<<<<<<< HEAD
{{ _("Documents") }}
@@ -318,7 +330,13 @@
+<<<<<<< HEAD +======= + + + +>>>>>>> e9e2fd3 (add bulk insert + po item insert)
@@ -361,26 +379,51 @@ {% endif %} {% comment %} {% endcomment %} +<<<<<<< HEAD +======= + +>>>>>>> e9e2fd3 (add bulk insert + po item insert) {{ _("Edit Order")}} {% if not saleorder.invoice %} {% comment %} {% endcomment %} +<<<<<<< HEAD {{ _("Create Invoice")}} +======= + + + {{ _("Create Invoice")}} + +>>>>>>> e9e2fd3 (add bulk insert + po item insert) {% endif %} {% if saleorder.status == 'approved' and not saleorder.actual_delivery_date %} +<<<<<<< HEAD {% endif %} {% if saleorder.status != 'cancelled' %} +<<<<<<< HEAD {% endif %}
@@ -532,8 +575,15 @@
@@ -562,8 +612,15 @@