From 53fc67b6d785c6bfe44422ce52647c5b9e72579b Mon Sep 17 00:00:00 2001 From: Mqdd Date: Thu, 20 Nov 2025 13:19:49 +0700 Subject: Import Using Excel --- indoteknik_custom/models/sourcing_job_order.py | 115 ++++++++++++++++--------- indoteknik_custom/views/sourcing.xml | 53 ++++++------ 2 files changed, 102 insertions(+), 66 deletions(-) diff --git a/indoteknik_custom/models/sourcing_job_order.py b/indoteknik_custom/models/sourcing_job_order.py index c0d15c75..347429bb 100644 --- a/indoteknik_custom/models/sourcing_job_order.py +++ b/indoteknik_custom/models/sourcing_job_order.py @@ -6,7 +6,7 @@ import logging import pytz from pytz import timezone import base64 -import csv +import xlrd import io _logger = logging.getLogger(__name__) @@ -239,6 +239,7 @@ class SourcingJobOrder(models.Model): self.env.context.get('from_action_take', False) or self.env.context.get('from_multi_action_take', False) or self.env.context.get('from_action_takeover', False) + or self.env.user.has_group('indoteknik_custom.group_role_it') ) if not ( @@ -1060,64 +1061,98 @@ class WizardExportSJOtoSO(models.TransientModel): 'target': 'current', } + class SourcingJobOrderLineImportWizard(models.TransientModel): _name = 'sourcing.job.order.line.import.wizard' - _description = 'Import Sourcing Job Order Line Wizard' + _description = 'Import SJO Line from Excel' - file = fields.Binary(string='File (.CSV)', required=True) + excel_file = fields.Binary("Excel File", required=True) filename = fields.Char("Filename") order_id = fields.Many2one('sourcing.job.order', string="Sourcing Job Order", required=True) + def action_import_excel(self): + if not self.excel_file: + raise UserError(_("⚠️ Harap upload file Excel terlebih dahulu.")) - def action_import(self): - if not self.file: - raise UserError("⚠️ Harap upload file terlebih dahulu.") - - data = base64.b64decode(self.file) - file_io = io.StringIO(data.decode('utf-8')) - reader = csv.DictReader(file_io) - + try: + data = base64.b64decode(self.excel_file) + book = xlrd.open_workbook(file_contents=data) + sheet = book.sheet_by_index(0) + except: + raise UserError(_("❌ Format Excel tidak valid atau rusak.")) + + header = [str(sheet.cell(0, col).value).strip() for col in range(sheet.ncols)] + required_headers = [ + 'Nama Barang', 'SKU', 'Expected Price', 'Note Sourcing', 'Brand', + 'Deskripsi / Spesifikasi', 'SLA Product', 'Quantity Product', + 'Purchase Price', 'Tax', 'Vendor', 'Product Category', + 'Categories', 'Product Type' + ] + + for req in required_headers: + if req not in header: + raise UserError(_("❌ Kolom '%s' tidak ditemukan di file Excel.") % req) + + header_map = {h: idx for idx, h in enumerate(header)} lines_created = 0 - for row in reader: - print("ROW:", row) # 🧪 Tambahkan ini untuk lihat isi row di log - _logger.info("ROW: %s", row) - - if not row.get('Nama Barang'): - continue - - vendor = self.env['res.partner'].search([('name', 'ilike', row.get('Vendor'))], limit=1) - tax = self.env['account.tax'].search([('name', 'ilike', row.get('Tax'))], limit=1) - product_category = self.env['product.category'].search([('name', 'ilike', row.get('Product Category'))], limit=1) - + ProductLine = self.env['sourcing.job.order.line'] + Tax = self.env['account.tax'] + Vendor = self.env['res.partner'] + Category = self.env['product.category'] + PublicCategory = self.env['product.public.category'] + + for row_idx in range(1, sheet.nrows): + row = sheet.row(row_idx) + def val(field): + return str(sheet.cell(row_idx, header_map[field]).value).strip() + + if not val('Nama Barang'): + continue # skip kosong + + # Relations + tax = Tax.search([('name', 'ilike', val('Tax'))], limit=1) + vendor = Vendor.search([('name', '=', val('Vendor'))], limit=1) + category = Category.search([('name', 'ilike', val('Product Category'))], limit=1) + + # Many2many: Categories + class_names = val('Categories').split(';') + class_ids = [] + for name in class_names: + name = name.strip() + if name: + pc = PublicCategory.search([('name', 'ilike', name)], limit=1) + if pc: + class_ids.append(pc.id) + + # Build values vals = { 'order_id': self.order_id.id, - 'product_name': row.get('Nama Barang'), - 'code': row.get('SKU'), - 'budget': row.get('Expected Price'), - 'note': row.get('Note Sourcing'), - 'brand': row.get('Brand'), - 'descriptions': row.get('Deskripsi / Spesifikasi'), - 'sla': row.get('SLA Product'), - 'quantity': float(row.get('Quantity Product') or 1.0), - 'price': float(row.get('Purchase Price') or 0.0), - 'vendor_id': vendor.id if vendor else False, + 'product_name': val('Nama Barang'), + 'code': val('SKU'), + 'budget': val('Expected Price'), + 'note': val('Note Sourcing'), + 'brand': val('Brand'), + 'descriptions': val('Deskripsi / Spesifikasi'), + 'sla': val('SLA Product'), + 'quantity': float(val('Quantity Product') or 0), + 'price': float(val('Purchase Price') or 0), 'tax_id': tax.id if tax else False, - 'product_category': product_category.id if product_category else False, - 'product_type': row.get('Product Type') or 'product', - # 'product_class': [(6, 0, classes.ids)], + 'vendor_id': vendor.id if vendor else False, + 'product_category': category.id if category else False, + 'product_type': val('Product Type') or 'product', + 'product_class': [(6, 0, class_ids)], } - print("Create line with:", vals) # 🧪 Debug log - self.env['sourcing.job.order.line'].create(vals) + ProductLine.create(vals) lines_created += 1 return { 'type': 'ir.actions.client', 'tag': 'display_notification', 'params': { - 'title': 'Import Selesai', - 'message': f'{lines_created} baris berhasil diimport.', + 'title': _('✅ Import Selesai'), + 'message': _('%s baris berhasil diimport.') % lines_created, 'type': 'success', 'sticky': False, } - } \ No newline at end of file + } diff --git a/indoteknik_custom/views/sourcing.xml b/indoteknik_custom/views/sourcing.xml index cc04b498..e5d239cb 100644 --- a/indoteknik_custom/views/sourcing.xml +++ b/indoteknik_custom/views/sourcing.xml @@ -49,32 +49,33 @@ - - sourcing.job.order.line.import.wizard.form - sourcing.job.order.line.import.wizard - -
- - - - -
- -
+
+
+
- -