summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMqdd <ahmadmiqdad27@gmail.com>2025-11-20 13:19:49 +0700
committerMqdd <ahmadmiqdad27@gmail.com>2025-11-20 13:19:49 +0700
commit53fc67b6d785c6bfe44422ce52647c5b9e72579b (patch)
tree094c776e8a2c1aab513dd4fafcc898468df5b111
parent32558c514e348321911fc201b38e9b9a398521e1 (diff)
<MIqdad> Import Using Excel
-rw-r--r--indoteknik_custom/models/sourcing_job_order.py115
-rw-r--r--indoteknik_custom/views/sourcing.xml53
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"