diff options
| author | Mqdd <ahmadmiqdad27@gmail.com> | 2025-11-20 13:19:49 +0700 |
|---|---|---|
| committer | Mqdd <ahmadmiqdad27@gmail.com> | 2025-11-20 13:19:49 +0700 |
| commit | 53fc67b6d785c6bfe44422ce52647c5b9e72579b (patch) | |
| tree | 094c776e8a2c1aab513dd4fafcc898468df5b111 | |
| parent | 32558c514e348321911fc201b38e9b9a398521e1 (diff) | |
<MIqdad> Import Using Excel
| -rw-r--r-- | indoteknik_custom/models/sourcing_job_order.py | 115 | ||||
| -rw-r--r-- | 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 @@ </field> </record> - <record id="view_sjo_line_import_wizard_form" model="ir.ui.view"> - <field name="name">sourcing.job.order.line.import.wizard.form</field> - <field name="model">sourcing.job.order.line.import.wizard</field> - <field name="arch" type="xml"> - <form string="Import SJO Line"> - <group> - <field name="order_id"/> - <field name="file" filename="filename"/> - </group> - <footer> - <!-- TOMBOL YANG PENTING: Ini yang akan memanggil def action_import --> - <button name="action_import" type="object" string="Import" class="btn-primary"/> +<record id="action_sjo_line_import_excel_wizard" model="ir.actions.act_window"> + <field name="name">Import SJO Line (Excel)</field> + <field name="res_model">sourcing.job.order.line.import.wizard</field> + <field name="view_mode">form</field> + <field name="target">new</field> + <field name="context">{'default_order_id': active_id}</field> +</record> + + +<record id="view_sjo_line_import_excel_wizard_form" model="ir.ui.view"> + <field name="name">sjo.line.import.excel.wizard.form</field> + <field name="model">sourcing.job.order.line.import.wizard</field> + <field name="arch" type="xml"> + <form string="Import SJO Line (.xls/.xlsx)"> + <group> + <field name="order_id" readonly="1"/> + <field name="excel_file" filename="filename"/> + <field name="filename" invisible="1"/> + </group> + <footer> + <button name="action_import_excel" type="object" string="Import" class="btn-primary"/> + <button string="Cancel" special="cancel" class="btn-secondary"/> + </footer> + </form> + </field> +</record> - <!-- Tombol cancel seperti biasa --> - <button string="Cancel" special="cancel" class="btn-secondary"/> - </footer> - </form> - </field> - </record> - - <record id="action_sjo_line_import_wizard" model="ir.actions.act_window"> - <field name="name">Import SJO Line</field> - <field name="res_model">sourcing.job.order.line.import.wizard</field> - <field name="view_mode">form</field> - <field name="target">new</field> - </record> <record id="view_wizard_export_sjo_to_so_form" model="ir.ui.view"> <field name="name">wizard.export.sjo.to.so.form</field> @@ -102,7 +103,7 @@ <field name="arch" type="xml"> <form string="Sourcing Job Order"> <header> - <button name="%(indoteknik_custom.action_sjo_line_import_wizard)d" string="Import Line" type="action" context="{'default_order_id': active_id}" class="btn-secondary"/> +<button name="%(action_sjo_line_import_excel_wizard)d" string="Import Excel" type="action" class="btn-secondary" icon="fa-upload" context="{'default_order_id': active_id}"/> <button name="action_take" string="Take" |
