From 6ba1731e740f7ad1e7aea38f723a2f431fec2c4b Mon Sep 17 00:00:00 2001 From: Mqdd Date: Mon, 1 Dec 2025 16:27:14 +0700 Subject: Initial Commit --- indoteknik_custom/models/find_page.py | 69 +++++++++++++++++++++++++++-------- indoteknik_custom/views/find_page.xml | 15 +++++++- 2 files changed, 67 insertions(+), 17 deletions(-) diff --git a/indoteknik_custom/models/find_page.py b/indoteknik_custom/models/find_page.py index 467e30d1..461e647d 100644 --- a/indoteknik_custom/models/find_page.py +++ b/indoteknik_custom/models/find_page.py @@ -4,8 +4,8 @@ import re import pysolr _logger = logging.getLogger(__name__) -_cat_brand_solr = pysolr.Solr('http://10.148.0.5:8983/solr/url_category_brand/', always_commit=True, timeout=30) -# _cat_brand_solr_dev = pysolr.Solr('http://127.0.0.1:8983/solr/url_category_brand/', always_commit=True, timeout=30) +# _cat_brand_solr = pysolr.Solr('http://10.148.0.5:8983/solr/url_category_brand/', always_commit=True, timeout=30) +_cat_brand_solr_dev = pysolr.Solr('http://localhost:8983/solr/url_category_brand', always_commit=True, timeout=30) class BrandProductCategory(models.Model): @@ -36,7 +36,20 @@ class FindPage(models.Model): brand_id = fields.Many2one('x_manufactures', string='Brand') category_id = fields.Many2one('product.public.category', string='Category', help='Bisa semua level Category') - url = fields.Char(string='Url') + url = fields.Char(string='Url', readonly=True) + keywords = fields.Char(string='Keywords') + + + @api.model + def create(self, vals): + record = super(FindPage, self).create(vals) + record._sync_to_solr() + return record + + def write(self, vals): + res = super(FindPage, self).write(vals) + self._sync_to_solr() + return res def _sync_to_solr(self, limit=10000): urls = self.env['web.find.page'].search([]) @@ -44,18 +57,27 @@ class FindPage(models.Model): catch = {} for url in urls: try: + keyword = self.keywords document = { 'id': url.id, 'category_id_i': url.category_id.id, 'brand_id_i': url.brand_id.id, - 'url_s': url.url + 'url_s': url.url, } + if keyword: + document['keyword_s'] = keyword documents.append(document) catch = document except Exception as e: _logger.error('Failed to add document to Solr URL Category Brand: %s', e) _logger.error('Document Data: %s', catch) - _cat_brand_solr.add(documents) + + try: + _cat_brand_solr_dev.add(documents) + except Exception as e: + _logger.error('Gagal sinkron ke Solr: %s', e) + return True + return True def _get_category_hierarchy(self, category): @@ -66,26 +88,38 @@ class FindPage(models.Model): current_category = current_category.parent_id return categories - def _generate_url(self): - categories = self.env['v.brand.product.category'].search([]) + def _generate_url(self): list_url = [] - for category in categories: - category_hierarchy = self._get_category_hierarchy(category.category_id) + keyword = self.keywords - for level, cat in enumerate(reversed(category_hierarchy), start=1): - list_url.append(self._generate_mod_url(cat, category.brand_id)) - # print(f"Level {level}: {cat.name} {category.brand_id.x_name}") + if keyword: + brands = self.env['x_manufactures'].search([('x_name', 'ilike', keyword)]) + categories = self.env['product.public.category'].search([('name', 'ilike', keyword)]) + for brand in brands: + for category in categories: + list_url.append(self._generate_mod_url(category, brand, keyword)) + else: + # Default: ambil semua kombinasi brand-category dari view + categories = self.env['v.brand.product.category'].search([]) + for category in categories: + category_hierarchy = self._get_category_hierarchy(category.category_id) + for level, cat in enumerate(reversed(category_hierarchy), start=1): + list_url.append(self._generate_mod_url(cat, category.brand_id)) + + # Hapus duplikat URL unique_list = [] for item in list_url: if item not in unique_list: unique_list.append(item) + count = 0 for item in unique_list: self._create_find_page(item['url'], item['category_id'], item['brand_id']) count += 1 - print(f"Total categories processed: {count}") + + _logger.info(f"Total URL generated: {count}") def _create_find_page(self, url, category_id, brand_id): param = { @@ -96,20 +130,25 @@ class FindPage(models.Model): find_page = self.env['web.find.page'].create(param) _logger.info('Created Web Find Page %s' % find_page.id) - def _generate_mod_url(self, category, brand): + def _generate_mod_url(self, category, brand, keyword): # generate_url = 'https://indoteknik.com/shop/find/category-brand' example cleaned_category = re.sub(r'[^\w\s]', '', category.name) cleaned_brand = re.sub(r'[^\w\s]', '', brand.x_name) cleaned_combined = cleaned_category+' '+cleaned_brand cleaned_combined = cleaned_combined.replace(' ', '-') cleaned_combined = cleaned_combined.replace('--', '-') - url = 'https://indoteknik.com/shop/find/'+cleaned_combined + if self.keywords: + url = 'https://indoteknik.com/shop/find/key='+keyword + else: + url = 'https://indoteknik.com/shop/find/'+cleaned_combined url = url.lower() result = { 'url': url, 'category_id': category.id, 'brand_id': brand.id } + if self.keywords: + result['keywords_s'] = keyword # print(url) # param = { # 'brand_id': brand.id, diff --git a/indoteknik_custom/views/find_page.xml b/indoteknik_custom/views/find_page.xml index c752aa98..65b0a570 100644 --- a/indoteknik_custom/views/find_page.xml +++ b/indoteknik_custom/views/find_page.xml @@ -7,6 +7,7 @@ + @@ -25,7 +26,8 @@ - + + @@ -62,9 +64,18 @@ tree,form + + + Sync to solr + + + code + model._sync_to_solr() + + - \ No newline at end of file + -- cgit v1.2.3 From c03a690a935508c2c669a5e7ff6da3c50f42fb53 Mon Sep 17 00:00:00 2001 From: Mqdd Date: Tue, 2 Dec 2025 09:15:20 +0700 Subject: change url mod gen --- indoteknik_custom/models/find_page.py | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/indoteknik_custom/models/find_page.py b/indoteknik_custom/models/find_page.py index 461e647d..5e530c64 100644 --- a/indoteknik_custom/models/find_page.py +++ b/indoteknik_custom/models/find_page.py @@ -130,31 +130,24 @@ class FindPage(models.Model): find_page = self.env['web.find.page'].create(param) _logger.info('Created Web Find Page %s' % find_page.id) - def _generate_mod_url(self, category, brand, keyword): - # generate_url = 'https://indoteknik.com/shop/find/category-brand' example + def _generate_mod_url(self, category, brand, keyword=None): cleaned_category = re.sub(r'[^\w\s]', '', category.name) cleaned_brand = re.sub(r'[^\w\s]', '', brand.x_name) - cleaned_combined = cleaned_category+' '+cleaned_brand - cleaned_combined = cleaned_combined.replace(' ', '-') - cleaned_combined = cleaned_combined.replace('--', '-') - if self.keywords: - url = 'https://indoteknik.com/shop/find/key='+keyword + cleaned_combined = f"{cleaned_category} {cleaned_brand}" + cleaned_combined = re.sub(r'\s+', '-', cleaned_combined.strip()) + + if keyword: + slugified_keyword = re.sub(r'\s+', '-', keyword.strip().lower()) + url = f"https://indoteknik.com/shop/find/key={slugified_keyword}" else: - url = 'https://indoteknik.com/shop/find/'+cleaned_combined - url = url.lower() + url = f"https://indoteknik.com/shop/find/{cleaned_combined.lower()}" + result = { 'url': url, 'category_id': category.id, 'brand_id': brand.id } - if self.keywords: - result['keywords_s'] = keyword - # print(url) - # param = { - # 'brand_id': brand.id, - # 'category_id': category.id, - # 'url':'' - # } - # self.env['web.find.page'].create() - # print(1) + if keyword: + result['keyword_s'] = keyword + return result -- cgit v1.2.3 From 0db7816b9208f9ef72bb4bb02e1d550cffb884ee Mon Sep 17 00:00:00 2001 From: Mqdd Date: Tue, 2 Dec 2025 10:31:41 +0700 Subject: set url not readonly and fix typo --- indoteknik_custom/models/find_page.py | 102 ++++++++++++++-------------------- indoteknik_custom/views/find_page.xml | 2 +- 2 files changed, 43 insertions(+), 61 deletions(-) diff --git a/indoteknik_custom/models/find_page.py b/indoteknik_custom/models/find_page.py index 5e530c64..e50ccd5e 100644 --- a/indoteknik_custom/models/find_page.py +++ b/indoteknik_custom/models/find_page.py @@ -5,7 +5,7 @@ import pysolr _logger = logging.getLogger(__name__) # _cat_brand_solr = pysolr.Solr('http://10.148.0.5:8983/solr/url_category_brand/', always_commit=True, timeout=30) -_cat_brand_solr_dev = pysolr.Solr('http://localhost:8983/solr/url_category_brand', always_commit=True, timeout=30) +_cat_brand_solr_dev = pysolr.Solr('http://127.0.0.1:8983/solr/url_category_brand/', always_commit=True, timeout=30) class BrandProductCategory(models.Model): @@ -34,50 +34,32 @@ class FindPage(models.Model): _inherit = ['mail.thread'] _rec_name = 'url' + keywords = fields.Char('Keywords') brand_id = fields.Many2one('x_manufactures', string='Brand') category_id = fields.Many2one('product.public.category', string='Category', help='Bisa semua level Category') - url = fields.Char(string='Url', readonly=True) - keywords = fields.Char(string='Keywords') - - - @api.model - def create(self, vals): - record = super(FindPage, self).create(vals) - record._sync_to_solr() - return record - - def write(self, vals): - res = super(FindPage, self).write(vals) - self._sync_to_solr() - return res + url = fields.Char(string='Url') def _sync_to_solr(self, limit=10000): urls = self.env['web.find.page'].search([]) documents = [] catch = {} + keywords = self.keywords for url in urls: try: - keyword = self.keywords document = { 'id': url.id, 'category_id_i': url.category_id.id, 'brand_id_i': url.brand_id.id, - 'url_s': url.url, + 'url_s': url.url } - if keyword: - document['keyword_s'] = keyword + if url.keywords: + document['keywords_s']= keywords documents.append(document) catch = document except Exception as e: _logger.error('Failed to add document to Solr URL Category Brand: %s', e) _logger.error('Document Data: %s', catch) - - try: - _cat_brand_solr_dev.add(documents) - except Exception as e: - _logger.error('Gagal sinkron ke Solr: %s', e) - return True - + _cat_brand_solr_dev.add(documents) return True def _get_category_hierarchy(self, category): @@ -88,66 +70,66 @@ class FindPage(models.Model): current_category = current_category.parent_id return categories - def _generate_url(self): - list_url = [] - keyword = self.keywords + categories = self.env['v.brand.product.category'].search([]) - if keyword: - brands = self.env['x_manufactures'].search([('x_name', 'ilike', keyword)]) - categories = self.env['product.public.category'].search([('name', 'ilike', keyword)]) + list_url = [] + for category in categories: + category_hierarchy = self._get_category_hierarchy(category.category_id) - for brand in brands: - for category in categories: - list_url.append(self._generate_mod_url(category, brand, keyword)) - else: - # Default: ambil semua kombinasi brand-category dari view - categories = self.env['v.brand.product.category'].search([]) - for category in categories: - category_hierarchy = self._get_category_hierarchy(category.category_id) - for level, cat in enumerate(reversed(category_hierarchy), start=1): - list_url.append(self._generate_mod_url(cat, category.brand_id)) + for level, cat in enumerate(reversed(category_hierarchy), start=1): + list_url.append(self._generate_mod_url(cat, category.brand_id)) + # print(f"Level {level}: {cat.name} {category.brand_id.x_name}") - # Hapus duplikat URL unique_list = [] for item in list_url: if item not in unique_list: unique_list.append(item) - count = 0 for item in unique_list: - self._create_find_page(item['url'], item['category_id'], item['brand_id']) + self._create_find_page(item['url'], item['category_id'], item['brand_id'], item['keywords']) count += 1 + print(f"Total categories processed: {count}") - _logger.info(f"Total URL generated: {count}") - - def _create_find_page(self, url, category_id, brand_id): + def _create_find_page(self, url, category_id, brand_id, keywords=None): param = { 'url': url, 'category_id': category_id, 'brand_id': brand_id, } + if keywords: + param['keywords'] = keywords find_page = self.env['web.find.page'].create(param) _logger.info('Created Web Find Page %s' % find_page.id) - def _generate_mod_url(self, category, brand, keyword=None): + def _generate_mod_url(self, category, brand, keywords=None): + # generate_url = 'https://indoteknik.com/shop/find/category-brand' example cleaned_category = re.sub(r'[^\w\s]', '', category.name) cleaned_brand = re.sub(r'[^\w\s]', '', brand.x_name) - cleaned_combined = f"{cleaned_category} {cleaned_brand}" - cleaned_combined = re.sub(r'\s+', '-', cleaned_combined.strip()) - - if keyword: - slugified_keyword = re.sub(r'\s+', '-', keyword.strip().lower()) - url = f"https://indoteknik.com/shop/find/key={slugified_keyword}" + cleaned_combined = cleaned_category+' '+cleaned_brand + cleaned_combined = cleaned_combined.replace(' ', '-') + cleaned_combined = cleaned_combined.replace('--', '-') + if keywords: + clean_keyword = re.sub(r'\s+', '-', keywords.strip().lower()) + # url = f"https://indoteknik.com/shop/find/key={clean_keyword}" + url = f"http://localhost:2100/shop/find/key={clean_keyword}" else: - url = f"https://indoteknik.com/shop/find/{cleaned_combined.lower()}" - + url = 'http://localhost:2100/shop/find/'+cleaned_combined + # url = 'https://indoteknik.com/shop/find/'+cleaned_combined + url = url.lower() result = { 'url': url, 'category_id': category.id, 'brand_id': brand.id } - if keyword: - result['keyword_s'] = keyword - + if keywords: + result['keywords_s'] = keywords + # print(url) + # param = { + # 'brand_id': brand.id, + # 'category_id': category.id, + # 'url':'' + # } + # self.env['web.find.page'].create() + # print(1) return result diff --git a/indoteknik_custom/views/find_page.xml b/indoteknik_custom/views/find_page.xml index 65b0a570..e333559e 100644 --- a/indoteknik_custom/views/find_page.xml +++ b/indoteknik_custom/views/find_page.xml @@ -27,7 +27,7 @@ - + -- cgit v1.2.3 From f21999829173fe8d2ef935f0564014fc04b8214e Mon Sep 17 00:00:00 2001 From: Mqdd Date: Tue, 2 Dec 2025 10:33:03 +0700 Subject: fix error --- indoteknik_custom/models/find_page.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/indoteknik_custom/models/find_page.py b/indoteknik_custom/models/find_page.py index e50ccd5e..561b15ee 100644 --- a/indoteknik_custom/models/find_page.py +++ b/indoteknik_custom/models/find_page.py @@ -43,7 +43,6 @@ class FindPage(models.Model): urls = self.env['web.find.page'].search([]) documents = [] catch = {} - keywords = self.keywords for url in urls: try: document = { @@ -53,7 +52,7 @@ class FindPage(models.Model): 'url_s': url.url } if url.keywords: - document['keywords_s']= keywords + document['keywords_s']= url.keywords documents.append(document) catch = document except Exception as e: -- cgit v1.2.3 From 9a16b32bb2fdd58739c410e7c4ea376ead340a32 Mon Sep 17 00:00:00 2001 From: Mqdd Date: Tue, 2 Dec 2025 10:41:46 +0700 Subject: fix generate url --- indoteknik_custom/models/find_page.py | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/indoteknik_custom/models/find_page.py b/indoteknik_custom/models/find_page.py index 561b15ee..46b69ff2 100644 --- a/indoteknik_custom/models/find_page.py +++ b/indoteknik_custom/models/find_page.py @@ -70,23 +70,41 @@ class FindPage(models.Model): return categories def _generate_url(self): - categories = self.env['v.brand.product.category'].search([]) + keyword = self.keywords list_url = [] - for category in categories: - category_hierarchy = self._get_category_hierarchy(category.category_id) + if not keyword: + categories = self.env['v.brand.product.category'].search([]) - for level, cat in enumerate(reversed(category_hierarchy), start=1): - list_url.append(self._generate_mod_url(cat, category.brand_id)) - # print(f"Level {level}: {cat.name} {category.brand_id.x_name}") + for category in categories: + category_hierarchy = self._get_category_hierarchy(category.category_id) + for level, cat in enumerate(reversed(category_hierarchy), start=1): + list_url.append( + self._generate_mod_url(cat, category.brand_id) # keywords=None otomatis + ) + else: + brands = self.env['x_manufactures'].search([('x_name', 'ilike', keyword)]) + categories = self.env['product.public.category'].search([('name', 'ilike', keyword)]) + + for brand in brands: + for category in categories: + list_url.append( + self._generate_mod_url(category, brand, keywords=keyword) + ) unique_list = [] for item in list_url: if item not in unique_list: unique_list.append(item) + count = 0 for item in unique_list: - self._create_find_page(item['url'], item['category_id'], item['brand_id'], item['keywords']) + self._create_find_page( + item['url'], + item['category_id'], + item['brand_id'], + item.get('keywords') + ) count += 1 print(f"Total categories processed: {count}") @@ -122,7 +140,7 @@ class FindPage(models.Model): 'brand_id': brand.id } if keywords: - result['keywords_s'] = keywords + result['keywords'] = keywords # print(url) # param = { # 'brand_id': brand.id, -- cgit v1.2.3 From fc226081de0bcf8e573307f94c08dfe4c3769b4d Mon Sep 17 00:00:00 2001 From: Mqdd Date: Tue, 2 Dec 2025 11:53:49 +0700 Subject: add import from excel --- indoteknik_custom/models/find_page.py | 77 +++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/indoteknik_custom/models/find_page.py b/indoteknik_custom/models/find_page.py index 46b69ff2..3968caed 100644 --- a/indoteknik_custom/models/find_page.py +++ b/indoteknik_custom/models/find_page.py @@ -2,6 +2,11 @@ from odoo import fields, models, api, tools, _ import logging import re import pysolr +from odoo.exceptions import UserError +import base64 +import xlrd, xlwt +import io + _logger = logging.getLogger(__name__) # _cat_brand_solr = pysolr.Solr('http://10.148.0.5:8983/solr/url_category_brand/', always_commit=True, timeout=30) @@ -39,6 +44,78 @@ class FindPage(models.Model): category_id = fields.Many2one('product.public.category', string='Category', help='Bisa semua level Category') url = fields.Char(string='Url') + def action_import_excel(self): + if not self.excel_file: + raise UserError(_("⚠️ Harap upload file Excel terlebih dahulu.")) + + 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 = [ + 'category', + 'brand', + 'keyword', + 'url', + ] + + 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 + PublicCategory = self.env['product.public.category'] + url = self.url + keywords = self.keywords + brand = self.env['x_manufactures'] + + 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('category'): + continue # skip kosong + + # Relations + brand = brand.search([('name', 'ilike', val('brand'))], 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 = { + 'brand_id': val('brand'), + 'keywords': val('keywords'), + 'url': val('url'), + 'category_id': val('category') + } + + lines_created += 1 + + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'title': _('✅ Import Selesai'), + 'message': _('%s baris berhasil diimport.') % lines_created, + 'type': 'success', + 'sticky': False, + } + } + def _sync_to_solr(self, limit=10000): urls = self.env['web.find.page'].search([]) documents = [] -- cgit v1.2.3 From 860187e158fff35cc8f6bd5169583ff5ccd78ffa Mon Sep 17 00:00:00 2001 From: Mqdd Date: Tue, 2 Dec 2025 17:15:23 +0700 Subject: remove import action --- indoteknik_custom/models/find_page.py | 72 ----------------------------------- 1 file changed, 72 deletions(-) diff --git a/indoteknik_custom/models/find_page.py b/indoteknik_custom/models/find_page.py index 3968caed..c31b8334 100644 --- a/indoteknik_custom/models/find_page.py +++ b/indoteknik_custom/models/find_page.py @@ -44,78 +44,6 @@ class FindPage(models.Model): category_id = fields.Many2one('product.public.category', string='Category', help='Bisa semua level Category') url = fields.Char(string='Url') - def action_import_excel(self): - if not self.excel_file: - raise UserError(_("⚠️ Harap upload file Excel terlebih dahulu.")) - - 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 = [ - 'category', - 'brand', - 'keyword', - 'url', - ] - - 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 - PublicCategory = self.env['product.public.category'] - url = self.url - keywords = self.keywords - brand = self.env['x_manufactures'] - - 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('category'): - continue # skip kosong - - # Relations - brand = brand.search([('name', 'ilike', val('brand'))], 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 = { - 'brand_id': val('brand'), - 'keywords': val('keywords'), - 'url': val('url'), - 'category_id': val('category') - } - - lines_created += 1 - - return { - 'type': 'ir.actions.client', - 'tag': 'display_notification', - 'params': { - 'title': _('✅ Import Selesai'), - 'message': _('%s baris berhasil diimport.') % lines_created, - 'type': 'success', - 'sticky': False, - } - } - def _sync_to_solr(self, limit=10000): urls = self.env['web.find.page'].search([]) documents = [] -- cgit v1.2.3 From 28355b77ffedbc87f9e8f211e2fed91c15022e4c Mon Sep 17 00:00:00 2001 From: Mqdd Date: Fri, 5 Dec 2025 14:50:18 +0700 Subject: balikin find page --- indoteknik_custom/models/find_page.py | 62 ++++++++--------------------------- 1 file changed, 13 insertions(+), 49 deletions(-) diff --git a/indoteknik_custom/models/find_page.py b/indoteknik_custom/models/find_page.py index c31b8334..467e30d1 100644 --- a/indoteknik_custom/models/find_page.py +++ b/indoteknik_custom/models/find_page.py @@ -2,15 +2,10 @@ from odoo import fields, models, api, tools, _ import logging import re import pysolr -from odoo.exceptions import UserError -import base64 -import xlrd, xlwt -import io - _logger = logging.getLogger(__name__) -# _cat_brand_solr = pysolr.Solr('http://10.148.0.5:8983/solr/url_category_brand/', always_commit=True, timeout=30) -_cat_brand_solr_dev = pysolr.Solr('http://127.0.0.1:8983/solr/url_category_brand/', always_commit=True, timeout=30) +_cat_brand_solr = pysolr.Solr('http://10.148.0.5:8983/solr/url_category_brand/', always_commit=True, timeout=30) +# _cat_brand_solr_dev = pysolr.Solr('http://127.0.0.1:8983/solr/url_category_brand/', always_commit=True, timeout=30) class BrandProductCategory(models.Model): @@ -39,7 +34,6 @@ class FindPage(models.Model): _inherit = ['mail.thread'] _rec_name = 'url' - keywords = fields.Char('Keywords') brand_id = fields.Many2one('x_manufactures', string='Brand') category_id = fields.Many2one('product.public.category', string='Category', help='Bisa semua level Category') url = fields.Char(string='Url') @@ -56,14 +50,12 @@ class FindPage(models.Model): 'brand_id_i': url.brand_id.id, 'url_s': url.url } - if url.keywords: - document['keywords_s']= url.keywords documents.append(document) catch = document except Exception as e: _logger.error('Failed to add document to Solr URL Category Brand: %s', e) _logger.error('Document Data: %s', catch) - _cat_brand_solr_dev.add(documents) + _cat_brand_solr.add(documents) return True def _get_category_hierarchy(self, category): @@ -75,77 +67,49 @@ class FindPage(models.Model): return categories def _generate_url(self): - keyword = self.keywords + categories = self.env['v.brand.product.category'].search([]) list_url = [] - if not keyword: - categories = self.env['v.brand.product.category'].search([]) - - for category in categories: - category_hierarchy = self._get_category_hierarchy(category.category_id) + for category in categories: + category_hierarchy = self._get_category_hierarchy(category.category_id) - for level, cat in enumerate(reversed(category_hierarchy), start=1): - list_url.append( - self._generate_mod_url(cat, category.brand_id) # keywords=None otomatis - ) - else: - brands = self.env['x_manufactures'].search([('x_name', 'ilike', keyword)]) - categories = self.env['product.public.category'].search([('name', 'ilike', keyword)]) + for level, cat in enumerate(reversed(category_hierarchy), start=1): + list_url.append(self._generate_mod_url(cat, category.brand_id)) + # print(f"Level {level}: {cat.name} {category.brand_id.x_name}") - for brand in brands: - for category in categories: - list_url.append( - self._generate_mod_url(category, brand, keywords=keyword) - ) unique_list = [] for item in list_url: if item not in unique_list: unique_list.append(item) - count = 0 for item in unique_list: - self._create_find_page( - item['url'], - item['category_id'], - item['brand_id'], - item.get('keywords') - ) + self._create_find_page(item['url'], item['category_id'], item['brand_id']) count += 1 print(f"Total categories processed: {count}") - def _create_find_page(self, url, category_id, brand_id, keywords=None): + def _create_find_page(self, url, category_id, brand_id): param = { 'url': url, 'category_id': category_id, 'brand_id': brand_id, } - if keywords: - param['keywords'] = keywords find_page = self.env['web.find.page'].create(param) _logger.info('Created Web Find Page %s' % find_page.id) - def _generate_mod_url(self, category, brand, keywords=None): + def _generate_mod_url(self, category, brand): # generate_url = 'https://indoteknik.com/shop/find/category-brand' example cleaned_category = re.sub(r'[^\w\s]', '', category.name) cleaned_brand = re.sub(r'[^\w\s]', '', brand.x_name) cleaned_combined = cleaned_category+' '+cleaned_brand cleaned_combined = cleaned_combined.replace(' ', '-') cleaned_combined = cleaned_combined.replace('--', '-') - if keywords: - clean_keyword = re.sub(r'\s+', '-', keywords.strip().lower()) - # url = f"https://indoteknik.com/shop/find/key={clean_keyword}" - url = f"http://localhost:2100/shop/find/key={clean_keyword}" - else: - url = 'http://localhost:2100/shop/find/'+cleaned_combined - # url = 'https://indoteknik.com/shop/find/'+cleaned_combined + url = 'https://indoteknik.com/shop/find/'+cleaned_combined url = url.lower() result = { 'url': url, 'category_id': category.id, 'brand_id': brand.id } - if keywords: - result['keywords'] = keywords # print(url) # param = { # 'brand_id': brand.id, -- cgit v1.2.3 From 8ee7bd9211a75809e1819de32ddf974c3736ebde Mon Sep 17 00:00:00 2001 From: Mqdd Date: Fri, 5 Dec 2025 16:46:24 +0700 Subject: Initial COmmit baru --- indoteknik_custom/__manifest__.py | 5 +- indoteknik_custom/models/__init__.py | 1 + indoteknik_custom/models/keywords.py | 52 ++++++++ indoteknik_custom/models/product_template.py | 171 +++++++++++++------------ indoteknik_custom/security/ir.model.access.csv | 1 + indoteknik_custom/views/find_page.xml | 2 - indoteknik_custom/views/keywords.xml | 55 ++++++++ 7 files changed, 198 insertions(+), 89 deletions(-) create mode 100644 indoteknik_custom/models/keywords.py create mode 100644 indoteknik_custom/views/keywords.xml diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py index 66962a24..dcb23344 100755 --- a/indoteknik_custom/__manifest__.py +++ b/indoteknik_custom/__manifest__.py @@ -90,7 +90,7 @@ 'views/product_sla.xml', 'views/voucher.xml', 'views/bill_receipt.xml', - 'views/account_financial_report_view.xml', + 'views/account_financial_report_view.xml', 'views/account_report_general_ledger_view.xml', 'views/account_move_multi_update.xml', 'views/airway_bill.xml', @@ -192,7 +192,8 @@ 'views/close_tempo_mail_template.xml', 'views/domain_apo.xml', 'views/uom_uom.xml', - 'views/commission_internal.xml' + 'views/commission_internal.xml', + 'views/keywords.xml' ], 'demo': [], 'css': [], diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py index a14c766e..328c76b0 100755 --- a/indoteknik_custom/models/__init__.py +++ b/indoteknik_custom/models/__init__.py @@ -165,3 +165,4 @@ from . import partial_delivery from . import domain_apo from . import uom_uom from . import commission_internal +from . import keywords diff --git a/indoteknik_custom/models/keywords.py b/indoteknik_custom/models/keywords.py new file mode 100644 index 00000000..38b6e2fe --- /dev/null +++ b/indoteknik_custom/models/keywords.py @@ -0,0 +1,52 @@ +from itertools import product +from odoo import fields, models, api, tools, _ +import logging +import re +import pysolr +from odoo.exceptions import UserError +import base64 +import xlrd, xlwt +import io + + +_logger = logging.getLogger(__name__) + +class Keywords(models.Model): + _name = 'keywords' + _order= 'id desc' + + category_id = fields.Many2one('product.public.category', string='Category') + keywords = fields.Char('Keywords') + product_ids = fields.One2many('product.product', 'keyword_id', string='Products') + + @api.constrains('product_ids', 'keywords', 'category_id') + def action_generate_products(self): + for record in self: + if not record.keywords: + continue + + domain = [ + ('name', 'ilike', record.keywords), + ('product_rating', '>=', 8), + ('unpublish', '=', False) + ] + + # if record.category_id: + # domain += [(record.product_ids.id, 'in', record.category_id.id)] + + matched_products = self.env['product.product'].search(domain) + + record.product_ids = [(6, 0, matched_products.ids)] + + _logger.info('Generated %s products for keyword "%s"', len(matched_products), record.keywords) + + @api.model + def create(self, vals): + record = super().create(vals) + record.action_generate_products() + return record + + def write(self, vals): + result = super().write(vals) + self.action_generate_products() + return result diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py index ee33a185..400d31e0 100755 --- a/indoteknik_custom/models/product_template.py +++ b/indoteknik_custom/models/product_template.py @@ -68,7 +68,7 @@ class ProductTemplate(models.Model): ('sp', 'Spare Part'), ('acc', 'Accessories') ], string='Kind of', copy=False) - sni = fields.Boolean(string='SNI') + sni = fields.Boolean(string='SNI') tkdn = fields.Boolean(string='TKDN') short_spesification = fields.Char(string='Short Spesification') merchandise_ok = fields.Boolean(string='Product Promotion') @@ -85,7 +85,7 @@ class ProductTemplate(models.Model): raise UserError('Hanya MD yang bisa membuat Product') result = super(ProductTemplate, self).create(vals) return result - + # def write(self, values): # group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id # users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])]) @@ -112,7 +112,7 @@ class ProductTemplate(models.Model): # qr_code_img = base64.b64encode(buffer.getvalue()).decode() # rec.qr_code = qr_code_img - + @api.constrains('name', 'internal_reference', 'x_manufacture') def required_public_categ_ids(self): for rec in self: @@ -125,7 +125,7 @@ class ProductTemplate(models.Model): def day_product_to_edit(self): day_products = [] - + for product in self: day_product = (product.write_date - product.create_date).days day_products.append(day_product) @@ -138,25 +138,25 @@ class ProductTemplate(models.Model): variants = product.product_variant_ids names = [x.name for x in variants] if variants else [product.name] default_codes = [x.default_code for x in variants] if variants else [product.default_code] - + domain = [ - ('default_code', '!=', False), + ('default_code', '!=', False), ('id', '!=', product.id), - '|', - ('name', 'in', names), + '|', + ('name', 'in', names), ('default_code', 'in', default_codes) ] - + product_exist = self.search(domain, limit=1) if len(product_exist) > 0: raise UserError('Name atau Internal Reference sudah digunakan pada produk lain') - + if self.env.user.is_purchasing_manager or self.env.user.is_editor_product or self.env.user.id in [1, 25]: continue - + if sum(product.day_product_to_edit()) > 0: raise UserError('Produk ini tidak dapat diubah') - + @api.constrains('name') def _validate_name(self): rule_regex = self.env['ir.config_parameter'].sudo().get_param('product.product.rule_name_regex') or '' @@ -165,7 +165,7 @@ class ProductTemplate(models.Model): pattern_suggest = rf"{rule_regex}" suggest = ''.join(re.findall(pattern_suggest, self.name)) raise UserError(f'Contoh yang benar adalah {suggest}') - + # def write(self, vals): # if 'solr_flag' not in vals and self.solr_flag == 1: # vals['solr_flag'] = 2 @@ -204,7 +204,7 @@ class ProductTemplate(models.Model): def unlink(self): if self._name == 'product.template': raise UserError('Maaf anda tidak bisa delete product') - + def update_new_product(self): current_time = datetime.now() delta_time = current_time - timedelta(days=30) @@ -235,7 +235,7 @@ class ProductTemplate(models.Model): for template in templates: if not template.default_code: template.default_code = 'IT.'+str(template.id) - + for variant in template.product_variant_ids: if not variant.default_code: variant.default_code = 'ITV.%s' % str(variant.id) @@ -327,7 +327,7 @@ class ProductTemplate(models.Model): def _get_stock_website(self): qty = self._get_stock_altama() print(qty) - + def get_stock_altama(self, item_code): current_time = datetime.now() current_time = current_time.strftime('%Y-%m-%d %H:%M:%S') @@ -338,7 +338,7 @@ class ProductTemplate(models.Model): token = token_data['access_token'] else: token = token_data.access_token - + url = "https://erpapi.altama.co.id/erp/api/stock/buffer/btob" auth = "Bearer "+token headers = { @@ -352,7 +352,7 @@ class ProductTemplate(models.Model): response = requests.post(url, headers=headers, json=json_data) if response.status_code != 200: return 0 - + datas = json.loads(response.text)['data'] qty = 0 for data in datas: @@ -371,12 +371,12 @@ class ProductTemplate(models.Model): data = { 'grant_type': 'client_credentials', } - + response = requests.post(url, headers=headers, data=data).json() lookup_json = json.dumps(response, indent=4, sort_keys=True) token = json.loads(lookup_json)['access_token'] expires_in = json.loads(lookup_json)['expires_in'] - + current_time = datetime.now() delta_time = current_time + timedelta(seconds=int(expires_in)) @@ -392,18 +392,18 @@ class ProductTemplate(models.Model): self.env['token.storage'].create([values]) return values - # ============================== + # ============================== def get_vendor_name(self, rec): """Get formatted name for vendor/supplier""" return rec.name.name if rec.name else f"ID {rec.id}" - + def get_attribute_line_name(self, rec): """Get formatted name for attribute line""" if rec.attribute_id and rec.value_ids: values = ", ".join(rec.value_ids.mapped('name')) return f"{rec.attribute_id.name}: {values}" return f"ID {rec.id}" - + def _get_vendor_field_label(self, field_name): """Get human-readable label for vendor fields""" field_labels = { @@ -419,10 +419,10 @@ class ProductTemplate(models.Model): 'date_end': 'End Date', 'min_qty': 'Quantity' } - return field_labels.get(field_name, field_name.replace('_', ' ').title()) + return field_labels.get(field_name, field_name.replace('_', ' ').title()) # ============================== - + def _collect_old_values(self, vals): """Collect old values before write""" return { @@ -433,7 +433,7 @@ class ProductTemplate(models.Model): } for record in self } - + def _prepare_attribute_line_info(self): """Prepare attribute line info for logging and update comparison""" line_info = {} @@ -446,7 +446,7 @@ class ProductTemplate(models.Model): 'value_names': ", ".join(line.value_ids.mapped('name')) } return line_info - + def _prepare_vendor_info(self): """Prepare vendor info for logging before they are deleted""" vendor_info = {} @@ -465,13 +465,13 @@ class ProductTemplate(models.Model): 'date_end': seller.date_end, } return vendor_info - + # ========================== - + def _get_context_with_all_info(self, vals): """Get context with all necessary info (attributes and vendors)""" context = dict(self.env.context) - + # Check for attribute line changes if 'attribute_line_ids' in vals: attribute_line_info = {} @@ -479,7 +479,7 @@ class ProductTemplate(models.Model): product_line_info = product._prepare_attribute_line_info() attribute_line_info.update(product_line_info) context['attribute_line_info'] = attribute_line_info - + # Check for vendor changes - store both for deletion and for comparing old values if 'seller_ids' in vals: vendor_info = {} @@ -488,18 +488,18 @@ class ProductTemplate(models.Model): # For deletion logging product_vendor_info = product._prepare_vendor_info() vendor_info.update(product_vendor_info) - + # For update comparison product_vendor_old = product._prepare_vendor_info() vendor_old_values.update(product_vendor_old) - + context['vendor_info'] = vendor_info context['vendor_old_values'] = vendor_old_values - + return context - + # ======================== - + def _log_image_changes(self, field_name, old_val, new_val): """Log image field changes""" label_map = { @@ -517,18 +517,18 @@ class ProductTemplate(models.Model): elif old_val != new_val: return f"
  • {label}: image updated
  • " return None - + def _log_attribute_line_changes(self, commands): """Log changes to attribute lines with complete information""" # Get stored info from context stored_info = self.env.context.get('attribute_line_info', {}) - + for cmd in commands: if cmd[0] == 0: # Add new = self.env['product.template.attribute.line'].new(cmd[2]) attribute_name = new.attribute_id.name if new.attribute_id else 'Attribute' values = ", ".join(new.value_ids.mapped('name')) if new.value_ids else '' - + message = f"Product Attribute:
    {attribute_name} added
    " if values: message += f"Values: '{values}'" @@ -537,7 +537,7 @@ class ProductTemplate(models.Model): elif cmd[0] == 1: # Update rec_id = cmd[1] vals = cmd[2] - + # Get old values from context old_data = stored_info.get(rec_id, {}) if not old_data: @@ -552,10 +552,10 @@ class ProductTemplate(models.Model): 'value_ids': [(v.id, v.name) for v in rec.value_ids], 'value_names': ", ".join(rec.value_ids.mapped('name')) } - + changes = [] attribute_name = old_data.get('attribute_name', 'Attribute') - + # Check for attribute change if 'attribute_id' in vals: old_attr = old_data.get('attribute_name', '-') @@ -567,7 +567,7 @@ class ProductTemplate(models.Model): # Check for value changes if 'value_ids' in vals: old_vals = old_data.get('value_names', '') - + # Parse the command for value_ids new_value_ids = [] for value_cmd in vals['value_ids']: @@ -579,14 +579,14 @@ class ProductTemplate(models.Model): elif value_cmd[0] == 3: # Remove # This is more complex, would need current state pass - + # Get new value names if new_value_ids: new_values = self.env['product.attribute.value'].browse(new_value_ids) new_vals = ", ".join(new_values.mapped('name')) else: new_vals = "" - + if old_vals != new_vals: changes.append(f"Values: '{old_vals}' → '{new_vals}'") @@ -596,7 +596,7 @@ class ProductTemplate(models.Model): message += "
    ".join(changes) self.message_post(body=message) - elif cmd[0] in (2, 3): # Remove + elif cmd[0] in (2, 3): # Remove # Use info from stored data line_data = stored_info.get(cmd[1]) if line_data: @@ -610,7 +610,7 @@ class ProductTemplate(models.Model): else: attribute_name = 'Attribute' values = f"ID {cmd[1]}" - + message = f"Product Attribute:
    {attribute_name} removed
    " if values: message += f"Values: '{values}'" @@ -618,25 +618,25 @@ class ProductTemplate(models.Model): elif cmd[0] == 5: # Clear all self.message_post(body=f"Product Attribute:
    All attributes removed") - + def _log_vendor_pricelist_changes(self, commands): """Log changes to vendor pricelist with complete information""" # Get stored info from context stored_info = self.env.context.get('vendor_info', {}) old_values_info = self.env.context.get('vendor_old_values', {}) - + for cmd in commands: if cmd[0] == 0: # Add vals = cmd[2] - + # Create temporary record to get proper display values temp_values = vals.copy() temp_values['product_tmpl_id'] = self.id new = self.env['product.supplierinfo'].new(temp_values) - + name = self.get_vendor_name(new) details = [] - + if 'price' in vals and vals['price'] is not None: details.append(f"
  • Price: {vals['price']}
  • ") if 'min_qty' in vals and vals['min_qty'] is not None: @@ -653,18 +653,18 @@ class ProductTemplate(models.Model): if 'product_uom' in vals and vals['product_uom']: uom = self.env['uom.uom'].browse(vals['product_uom']) details.append(f"
  • Unit of Measure: {uom.name}
  • ") - + if details: detail_str = f" with:
      {''.join(details)}
    " else: detail_str = "" - + self.message_post(body=f"Vendor Pricelist: added '{name}'{detail_str}") - + elif cmd[0] == 1: # Update rec_id = cmd[1] vals = cmd[2] - + # Get old values from context old_data = old_values_info.get(rec_id, {}) if not old_data: @@ -685,10 +685,10 @@ class ProductTemplate(models.Model): 'date_start': rec.date_start, 'date_end': rec.date_end, } - + name = old_data.get('name', f'ID {rec_id}') changes = [] - + # Check each field in vals for changes for field, new_value in vals.items(): if field == 'name': @@ -698,9 +698,9 @@ class ProductTemplate(models.Model): new_name = self.env['res.partner'].browse(new_value).name if new_value else 'None' changes.append(f"
  • Vendor: {old_name} → {new_name}
  • ") continue - + old_value = old_data.get(field) - + # Format values based on field type if field == 'currency_id': if old_value != new_value: @@ -732,14 +732,14 @@ class ProductTemplate(models.Model): # Compare numeric values properly old_num = float(old_value) if old_value is not None else 0.0 new_num = float(new_value) if new_value is not None else 0.0 - + if field == 'delay': # Integer field old_num = int(old_num) new_num = int(new_num) - + if old_num == new_num: continue - + old_str = str(old_value) if old_value is not None else 'None' new_str = str(new_value) if new_value is not None else 'None' else: @@ -748,20 +748,20 @@ class ProductTemplate(models.Model): continue old_str = str(old_value) if old_value is not None else 'None' new_str = str(new_value) if new_value is not None else 'None' - + label = self._get_vendor_field_label(field) changes.append(f"
  • {label}: {old_str} → {new_str}
  • ") - + if changes: changes_str = f"
      {''.join(changes)}
    " self.message_post(body=f"Vendor Pricelist: updated '{name}':{changes_str}") - + elif cmd[0] in (2, 3): # Remove vendor_data = stored_info.get(cmd[1]) if vendor_data: name = vendor_data['name'] details = [] - + if vendor_data.get('price'): details.append(f"
  • Price: {vendor_data['price']}
  • ") if vendor_data.get('min_qty'): @@ -770,7 +770,7 @@ class ProductTemplate(models.Model): details.append(f"
  • Product Name: {vendor_data['product_name']}
  • ") if vendor_data.get('delay'): details.append(f"
  • Delivery Lead Time: {vendor_data['delay']}
  • ") - + if details: detail_str = f"
      {''.join(details)}
    " else: @@ -786,7 +786,7 @@ class ProductTemplate(models.Model): details.append(f"
  • Quantity: {rec.min_qty}
  • ") if rec.product_name: details.append(f"
  • Product Name: {rec.product_name}
  • ") - + if details: detail_str = f"
      {''.join(details)}
    " else: @@ -794,12 +794,12 @@ class ProductTemplate(models.Model): else: name = f"ID {cmd[1]}" detail_str = "" - + self.message_post(body=f"Vendor Pricelist: removed '{name}'{detail_str}") - + elif cmd[0] == 5: # Clear all self.message_post(body=f"Vendor Pricelist: all removed") - + def _log_field_changes_product(self, vals, old_values): """Revised - Log general field changes for product template without posting to variants""" exclude_fields = ['solr_flag', 'desc_update_solr', 'last_update_solr', 'is_edited'] @@ -891,7 +891,7 @@ class ProductTemplate(models.Model): # # raise UserError('Tidak dapat mengubah produk sementara') # self._log_field_changes(vals) # return super(ProductTemplate, self).write(vals) - + class ProductProduct(models.Model): _inherit = "product.product" web_price = fields.Float( @@ -929,6 +929,7 @@ class ProductProduct(models.Model): qr_code_variant = fields.Binary("QR Code Variant", compute='_compute_qr_code_variant') qty_pcs_box = fields.Float("Pcs Box") barcode_box = fields.Char("Barcode Box") + keyword_id = fields.Many2one('keywords', string='Keyword') def generate_product_sla(self): product_variant_ids = self.env.context.get('active_ids', []) @@ -951,7 +952,7 @@ class ProductProduct(models.Model): raise UserError('Hanya MD yang bisa membuat Product') result = super(ProductProduct, self).create(vals) return result - + # def write(self, values): # group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id # active_model = self.env.context.get('active_model') @@ -967,7 +968,7 @@ class ProductProduct(models.Model): if not rec.active: rec.qr_code_variant = False # Clear the QR Code for archived variants continue - + qr = qrcode.QRCode( version=1, error_correction=qrcode.constants.ERROR_CORRECT_L, @@ -1055,11 +1056,11 @@ class ProductProduct(models.Model): if self.qty_available_bandengan < qty_purchase: return 'harus beli' return 'masih cukup' - + def _get_qty_upcoming(self): for product in self: product.qty_upcoming = product.incoming_qty + product.qty_available - + def _get_qty_sold(self): for product in self: order_line = self.env['sale.order.line'].search([ @@ -1070,13 +1071,13 @@ class ProductProduct(models.Model): def day_product_to_edit(self): day_products = [] - + for product in self: day_product = (product.write_date - product.create_date).days day_products.append(day_product) return day_products - + @api.constrains('name') def _validate_name(self): rule_regex = self.env['ir.config_parameter'].sudo().get_param('product.product.rule_name_regex') or '' @@ -1085,7 +1086,7 @@ class ProductProduct(models.Model): pattern_suggest = rf"{rule_regex}" suggest = ''.join(re.findall(pattern_suggest, self.name)) raise UserError(f'Contoh yang benar adalah {suggest}') - + def _get_qty_incoming_bandengan(self): for product in self: qty = self.env['v.move.outstanding'].read_group( @@ -1207,11 +1208,11 @@ class ProductProduct(models.Model): for product in self: stock_vendor = self.env['stock.vendor'].search([('product_variant_id', '=', product.id)], limit=1) product.qty_stock_vendor = stock_vendor.quantity + product.qty_available - + def unlink(self): if self._name == 'product.product': raise UserError('Maaf anda tidak bisa delete product') - + def _get_active_flash_sale(self): current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') pricelist = self.env['product.pricelist'].search([ @@ -1237,7 +1238,7 @@ class ProductProduct(models.Model): def _log_field_changes_product_variants(self, vals, old_values): """Revised - Log field changes for variants without posting to template""" exclude_fields = ['solr_flag', 'desc_update_solr', 'last_update_solr', 'is_edited'] - + # Custom labels for image fields custom_labels = { 'image_1920': 'Main Image', @@ -1314,7 +1315,7 @@ class ProductProduct(models.Model): 'public_categ_ids', 'search_rank', 'search_rank_weekly', 'image_1920', 'unpublished', 'image_carousel_lines' ] - + if any(field in vals for field in tracked_fields): old_values = self._collect_old_values(vals) result = super().write(vals) @@ -1343,7 +1344,7 @@ class OutstandingMove(models.Model): tools.drop_view_if_exists(self.env.cr, self._table) self.env.cr.execute(""" CREATE OR REPLACE VIEW %s AS - select sm.id, sm.reference, sm.product_id, + select sm.id, sm.reference, sm.product_id, sm.product_uom_qty as qty_need, sm.location_id, sm.location_dest_id, sm.raw_material_production_id as mo_id, diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv index bc8dc2a4..796e5275 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -214,3 +214,4 @@ access_surat_piutang_user,surat.piutang user,model_surat_piutang,,1,1,1,1 access_surat_piutang_line_user,surat.piutang.line user,model_surat_piutang_line,,1,1,1,1 access_sj_tele,access.sj.tele,model_sj_tele,base.group_system,1,1,1,1 access_stock_picking_sj_document,stock.picking.sj.document,model_stock_picking_sj_document,base.group_user,1,1,1,1 +access_keywords,keywords,model_keywords,base.group_user,1,1,1,1 diff --git a/indoteknik_custom/views/find_page.xml b/indoteknik_custom/views/find_page.xml index e333559e..fc9bddbb 100644 --- a/indoteknik_custom/views/find_page.xml +++ b/indoteknik_custom/views/find_page.xml @@ -7,7 +7,6 @@ - @@ -26,7 +25,6 @@ - diff --git a/indoteknik_custom/views/keywords.xml b/indoteknik_custom/views/keywords.xml new file mode 100644 index 00000000..872887a7 --- /dev/null +++ b/indoteknik_custom/views/keywords.xml @@ -0,0 +1,55 @@ + + + + keywords.tree + keywords + + + + + + + + + + + keywords.form + keywords + +
    + + + + + + + +
    +
    +
    + + + keywords.list.select + keywords + + + + + + + + + + + Keywords + ir.actions.act_window + keywords + + tree,form + + +
    -- cgit v1.2.3 From e27101a407ad9df9b341149b2b5ff5120062a464 Mon Sep 17 00:00:00 2001 From: Mqdd Date: Mon, 8 Dec 2025 13:03:38 +0700 Subject: done temp --- indoteknik_custom/models/keywords.py | 69 +++++++++++++++++++++++++++--------- indoteknik_custom/views/keywords.xml | 1 + 2 files changed, 53 insertions(+), 17 deletions(-) diff --git a/indoteknik_custom/models/keywords.py b/indoteknik_custom/models/keywords.py index 38b6e2fe..6e34642a 100644 --- a/indoteknik_custom/models/keywords.py +++ b/indoteknik_custom/models/keywords.py @@ -15,38 +15,73 @@ class Keywords(models.Model): _name = 'keywords' _order= 'id desc' - category_id = fields.Many2one('product.public.category', string='Category') - keywords = fields.Char('Keywords') - product_ids = fields.One2many('product.product', 'keyword_id', string='Products') + category_id = fields.Many2one('product.public.category', string='Category', required=True) + keywords = fields.Char('Keywords', required=True) + product_ids = fields.Many2many( + 'product.product', + 'keywords_product_rel', + 'keyword_id', + 'product_id', + string='Products' + ) + skip = fields.Boolean('Skip Generate Product', default=False) - @api.constrains('product_ids', 'keywords', 'category_id') - def action_generate_products(self): + + def generate_products(self): + """Generate product_ids menggunakan SQL mentah (super cepat).""" for record in self: if not record.keywords: continue - domain = [ - ('name', 'ilike', record.keywords), - ('product_rating', '>=', 8), - ('unpublish', '=', False) - ] + keyword = "%" + record.keywords.strip() + "%" + + # Query dasar + sql = """ + SELECT pp.id + FROM product_product pp + JOIN product_template pt ON pt.id = pp.product_tmpl_id + LEFT JOIN product_public_category_product_template_rel rel + ON rel.product_template_id = pt.id + WHERE ( + pt.name ILIKE %s + OR pt.website_description ILIKE %s + AND pt.unpublished = False + AND pt.product_rating >= 8 + ) + """ + + params = [keyword, keyword] + + # Filter kategori kalau ada + if record.category_id: + sql += " AND rel.product_public_category_id = %s" + params.append(record.category_id.id) - # if record.category_id: - # domain += [(record.product_ids.id, 'in', record.category_id.id)] + # Eksekusi SQL + self.env.cr.execute(sql, params) + rows = self.env.cr.fetchall() - matched_products = self.env['product.product'].search(domain) + product_ids = [r[0] for r in rows] - record.product_ids = [(6, 0, matched_products.ids)] + # Update Many2many + record.with_context(skip_generate=True).write({ + 'product_ids': [(6, 0, product_ids)] + }) - _logger.info('Generated %s products for keyword "%s"', len(matched_products), record.keywords) + _logger.info( + "SQL FAST MODE: Found %s products for keyword '%s'", + len(product_ids), + record.keywords + ) @api.model def create(self, vals): record = super().create(vals) - record.action_generate_products() + record.generate_products() return record def write(self, vals): result = super().write(vals) - self.action_generate_products() + if not self.env.context.get("skip_generate") and self.skip == False: + self.generate_products() return result diff --git a/indoteknik_custom/views/keywords.xml b/indoteknik_custom/views/keywords.xml index 872887a7..77bb8036 100644 --- a/indoteknik_custom/views/keywords.xml +++ b/indoteknik_custom/views/keywords.xml @@ -22,6 +22,7 @@ +
    -- cgit v1.2.3 From f71156bc5e0ce9ceb22215a4345518b02f5250ed Mon Sep 17 00:00:00 2001 From: Mqdd Date: Mon, 8 Dec 2025 15:19:36 +0700 Subject: solr --- indoteknik_custom/models/keywords.py | 76 ++++++++++++++++++++++++++++++++---- indoteknik_custom/views/keywords.xml | 15 ++++++- 2 files changed, 82 insertions(+), 9 deletions(-) diff --git a/indoteknik_custom/models/keywords.py b/indoteknik_custom/models/keywords.py index 6e34642a..b1f2642d 100644 --- a/indoteknik_custom/models/keywords.py +++ b/indoteknik_custom/models/keywords.py @@ -7,9 +7,12 @@ from odoo.exceptions import UserError import base64 import xlrd, xlwt import io +from bs4 import BeautifulSoup _logger = logging.getLogger(__name__) +# solr = pysolr.Solr('http://10.148.0.5:8983/solr/keywords/', always_commit=True, timeout=30) +solr = pysolr.Solr('http://127.0.0.1:8983/solr/keywords/', always_commit=True, timeout=30) class Keywords(models.Model): _name = 'keywords' @@ -24,18 +27,22 @@ class Keywords(models.Model): 'product_id', string='Products' ) + name = fields.Char('Name', compute="_compute_name") skip = fields.Boolean('Skip Generate Product', default=False) + def _compute_name(self): + for record in self: + if not record.name: + record.name = record.keywords + def generate_products(self): - """Generate product_ids menggunakan SQL mentah (super cepat).""" for record in self: if not record.keywords: continue keyword = "%" + record.keywords.strip() + "%" - # Query dasar sql = """ SELECT pp.id FROM product_product pp @@ -52,28 +59,81 @@ class Keywords(models.Model): params = [keyword, keyword] - # Filter kategori kalau ada if record.category_id: sql += " AND rel.product_public_category_id = %s" params.append(record.category_id.id) - # Eksekusi SQL - self.env.cr.execute(sql, params) + # Exec query rows = self.env.cr.fetchall() product_ids = [r[0] for r in rows] - # Update Many2many record.with_context(skip_generate=True).write({ 'product_ids': [(6, 0, product_ids)] }) _logger.info( - "SQL FAST MODE: Found %s products for keyword '%s'", + "SQL : Found %s products for keyword '%s'", len(product_ids), record.keywords ) + + def sync_solr(self): + solr = self.env['apache.solr'] # pastikan ini model Solr sudah ada + documents = [] + + # Sync Keywords + for keyword in self.search([]): + doc = { + 'id': f'keyword_{keyword.id}', # prefix biar unik di Solr + 'type_s': 'keyword', + 'name_s': keyword.keywords, + 'category_id_i': keyword.category_id.id if keyword.category_id else 0, + 'product_ids_ii': keyword.product_ids.ids, + } + documents.append(doc) + _logger.info('Prepared Solr document for keyword %s', keyword.keywords) + + # Sync Products + products = self.env['product.product'].search([]) + for product in products: + category_ids = [c.id for c in product.public_categ_ids] + doc = { + 'id': f'product_{product.id}', + 'type_s': 'product', + 'name_s': product.name, + 'default_code_s': product.default_code or '', + 'category_ids_ii': category_ids, + 'website_description_t': product.website_description or '', + 'product_rating_f': getattr(product, 'virtual_rating', 0), + 'active_b': product.active, + } + documents.append(doc) + _logger.info('Prepared Solr document for product %s', product.name) + + # Sync Categories + categories = self.env['product.public.category'].search([]) + for cat in categories: + doc = { + 'id': f'category_{cat.id}', + 'type_s': 'category', + 'name_s': cat.name, + 'parent_id_i': cat.parent_id.id if cat.parent_id else 0, + } + documents.append(doc) + _logger.info('Prepared Solr document for category %s', cat.name) + + # Kirim ke Solr + if documents: + try: + solr.add(documents, softCommit=True) + _logger.info('Synced %d documents to Solr', len(documents)) + except Exception as e: + _logger.error('Failed to sync documents to Solr: %s', e) + else: + _logger.warning('No documents to sync to Solr') + @api.model def create(self, vals): record = super().create(vals) @@ -82,6 +142,6 @@ class Keywords(models.Model): def write(self, vals): result = super().write(vals) - if not self.env.context.get("skip_generate") and self.skip == False: + if not self.env.context.get("skip_generate") and not self.skip: self.generate_products() return result diff --git a/indoteknik_custom/views/keywords.xml b/indoteknik_custom/views/keywords.xml index 77bb8036..2fc44ced 100644 --- a/indoteknik_custom/views/keywords.xml +++ b/indoteknik_custom/views/keywords.xml @@ -18,6 +18,11 @@
    +
    +

    + +

    +
    @@ -41,13 +46,21 @@ - + Keywords ir.actions.act_window keywords tree,form + + + Sync to solr + + + code + model.sync_solr() + Date: Tue, 9 Dec 2025 09:20:20 +0700 Subject: solr & fix cannot fetch data --- indoteknik_custom/models/keywords.py | 91 ++++++++++++++---------------------- 1 file changed, 36 insertions(+), 55 deletions(-) diff --git a/indoteknik_custom/models/keywords.py b/indoteknik_custom/models/keywords.py index b1f2642d..de7636b7 100644 --- a/indoteknik_custom/models/keywords.py +++ b/indoteknik_custom/models/keywords.py @@ -35,6 +35,16 @@ class Keywords(models.Model): if not record.name: record.name = record.keywords + # @api.constrains('keywords') + # def check_already_exist(self): + # model = self.env['keywords'] + # for record in self: + # match = model.search([ + # ('name', 'ilike', record.name) + # ]) + # if match: + # raise UserError("Tidak bisa create/write/duplicate karena keywords sudah dipakai") + def generate_products(self): for record in self: @@ -43,6 +53,7 @@ class Keywords(models.Model): keyword = "%" + record.keywords.strip() + "%" + # Query dasar sql = """ SELECT pp.id FROM product_product pp @@ -59,11 +70,16 @@ class Keywords(models.Model): params = [keyword, keyword] + # Filter kategori ke child if record.category_id: - sql += " AND rel.product_public_category_id = %s" - params.append(record.category_id.id) - - # Exec query + child_categs = self.env['product.public.category'].search([ + ('id', 'child_of', record.category_id.id) + ]) + sql += " AND rel.product_public_category_id = ANY(%s)" + params.append(child_categs.ids) + + # Exec SQL + self.env.cr.execute(sql, params) rows = self.env.cr.fetchall() product_ids = [r[0] for r in rows] @@ -73,66 +89,31 @@ class Keywords(models.Model): }) _logger.info( - "SQL : Found %s products for keyword '%s'", + "Product Found: Found %s products for keyword '%s'", len(product_ids), record.keywords ) - def sync_solr(self): - solr = self.env['apache.solr'] # pastikan ini model Solr sudah ada + # solr_model = self.env['apache.solr'] documents = [] - - # Sync Keywords + data = {} for keyword in self.search([]): - doc = { - 'id': f'keyword_{keyword.id}', # prefix biar unik di Solr - 'type_s': 'keyword', - 'name_s': keyword.keywords, - 'category_id_i': keyword.category_id.id if keyword.category_id else 0, - 'product_ids_ii': keyword.product_ids.ids, - } - documents.append(doc) - _logger.info('Prepared Solr document for keyword %s', keyword.keywords) - - # Sync Products - products = self.env['product.product'].search([]) - for product in products: - category_ids = [c.id for c in product.public_categ_ids] - doc = { - 'id': f'product_{product.id}', - 'type_s': 'product', - 'name_s': product.name, - 'default_code_s': product.default_code or '', - 'category_ids_ii': category_ids, - 'website_description_t': product.website_description or '', - 'product_rating_f': getattr(product, 'virtual_rating', 0), - 'active_b': product.active, - } - documents.append(doc) - _logger.info('Prepared Solr document for product %s', product.name) - - # Sync Categories - categories = self.env['product.public.category'].search([]) - for cat in categories: - doc = { - 'id': f'category_{cat.id}', - 'type_s': 'category', - 'name_s': cat.name, - 'parent_id_i': cat.parent_id.id if cat.parent_id else 0, - } - documents.append(doc) - _logger.info('Prepared Solr document for category %s', cat.name) - - # Kirim ke Solr - if documents: try: - solr.add(documents, softCommit=True) - _logger.info('Synced %d documents to Solr', len(documents)) + doc = { + 'id': f'keyword_{keyword.id}', + 'type_s': 'keyword', + 'name_s': keyword.keywords, + 'category_id_i': keyword.category_id.id if keyword.category_id else 0, + 'product_ids_ii': keyword.product_ids.ids, + } + documents.append(doc) + data = doc except Exception as e: - _logger.error('Failed to sync documents to Solr: %s', e) - else: - _logger.warning('No documents to sync to Solr') + _logger.error('failed %s', e) + _logger.error('doc data: %s', data) + solr.add(documents) + return True @api.model def create(self, vals): -- cgit v1.2.3 From ea7edf0fee4949203e94360882590ea0c9bb3b0b Mon Sep 17 00:00:00 2001 From: Mqdd Date: Tue, 9 Dec 2025 10:32:27 +0700 Subject: done solr --- indoteknik_custom/models/keywords.py | 27 +++++++++++++++++++++------ indoteknik_custom/views/keywords.xml | 2 ++ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/indoteknik_custom/models/keywords.py b/indoteknik_custom/models/keywords.py index de7636b7..fd8603c6 100644 --- a/indoteknik_custom/models/keywords.py +++ b/indoteknik_custom/models/keywords.py @@ -11,8 +11,8 @@ from bs4 import BeautifulSoup _logger = logging.getLogger(__name__) -# solr = pysolr.Solr('http://10.148.0.5:8983/solr/keywords/', always_commit=True, timeout=30) -solr = pysolr.Solr('http://127.0.0.1:8983/solr/keywords/', always_commit=True, timeout=30) +# solr = pysolr.Solr('http://10.148.0.5:8983/solr/searchkey/', always_commit=True, timeout=30) +solr = pysolr.Solr('http://127.0.0.1:8983/solr/searchkey/', always_commit=True, timeout=30) class Keywords(models.Model): _name = 'keywords' @@ -29,6 +29,18 @@ class Keywords(models.Model): ) name = fields.Char('Name', compute="_compute_name") skip = fields.Boolean('Skip Generate Product', default=False) + url = fields.Char('Website URL', compute="_compute_url") + + @api.depends('keywords') + def _compute_url(self): + prefix = "https://indoteknik.com/searchkey/" + for record in self: + if record.keywords: + slug = re.sub(r'[^a-zA-Z0-9]+', '-', record.keywords.strip().lower()) + slug = slug.strip('-') + record.url = prefix + slug + else: + record.url = False def _compute_name(self): for record in self: @@ -84,6 +96,9 @@ class Keywords(models.Model): product_ids = [r[0] for r in rows] + if not product_ids: + raise UserError("Tidak berhasil menemukan barang") + record.with_context(skip_generate=True).write({ 'product_ids': [(6, 0, product_ids)] }) @@ -101,11 +116,11 @@ class Keywords(models.Model): for keyword in self.search([]): try: doc = { - 'id': f'keyword_{keyword.id}', + 'id': keyword.id, 'type_s': 'keyword', - 'name_s': keyword.keywords, - 'category_id_i': keyword.category_id.id if keyword.category_id else 0, - 'product_ids_ii': keyword.product_ids.ids, + 'keywords_t': keyword.keywords, + 'url_s': keyword.url, + 'product_ids_is': keyword.product_ids.ids, } documents.append(doc) data = doc diff --git a/indoteknik_custom/views/keywords.xml b/indoteknik_custom/views/keywords.xml index 2fc44ced..9145ec47 100644 --- a/indoteknik_custom/views/keywords.xml +++ b/indoteknik_custom/views/keywords.xml @@ -7,6 +7,7 @@ + @@ -26,6 +27,7 @@ + -- cgit v1.2.3 From c13ed8b1f00d513bec387c614ac785c7c91debf7 Mon Sep 17 00:00:00 2001 From: Mqdd Date: Wed, 10 Dec 2025 07:53:30 +0700 Subject: Add count product --- indoteknik_custom/models/keywords.py | 14 +++++++++++--- indoteknik_custom/views/keywords.xml | 2 ++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/indoteknik_custom/models/keywords.py b/indoteknik_custom/models/keywords.py index fd8603c6..a8d2fce6 100644 --- a/indoteknik_custom/models/keywords.py +++ b/indoteknik_custom/models/keywords.py @@ -30,6 +30,12 @@ class Keywords(models.Model): name = fields.Char('Name', compute="_compute_name") skip = fields.Boolean('Skip Generate Product', default=False) url = fields.Char('Website URL', compute="_compute_url") + sum = fields.Integer('Total Product', compute="_compute_total_product", readonly=True) + + @api.depends('product_ids') + def _compute_total_product(self): + for record in self: + record.sum = len(record.product_ids) @api.depends('keywords') def _compute_url(self): @@ -114,13 +120,15 @@ class Keywords(models.Model): documents = [] data = {} for keyword in self.search([]): + searchkey = (keyword.keywords or '').strip().lower().replace(' ', '-') try: doc = { 'id': keyword.id, - 'type_s': 'keyword', - 'keywords_t': keyword.keywords, + # 'type_s': 'keyword', + 'keywords_s': searchkey, + # 'searchkey_t': searchkey, 'url_s': keyword.url, - 'product_ids_is': keyword.product_ids.ids, + 'product_ids_is': [p.product_tmpl_id.id for p in keyword.product_ids] } documents.append(doc) data = doc diff --git a/indoteknik_custom/views/keywords.xml b/indoteknik_custom/views/keywords.xml index 9145ec47..f5374655 100644 --- a/indoteknik_custom/views/keywords.xml +++ b/indoteknik_custom/views/keywords.xml @@ -8,6 +8,7 @@ + @@ -28,6 +29,7 @@ + -- cgit v1.2.3 From 2ae552a716040d65690bd20e6b1854d5201d32bd Mon Sep 17 00:00:00 2001 From: Mqdd Date: Wed, 10 Dec 2025 13:06:25 +0700 Subject: throw category to solr --- indoteknik_custom/models/keywords.py | 1 + 1 file changed, 1 insertion(+) diff --git a/indoteknik_custom/models/keywords.py b/indoteknik_custom/models/keywords.py index a8d2fce6..cee189c4 100644 --- a/indoteknik_custom/models/keywords.py +++ b/indoteknik_custom/models/keywords.py @@ -125,6 +125,7 @@ class Keywords(models.Model): doc = { 'id': keyword.id, # 'type_s': 'keyword', + 'category_id_i': keyword.category_id.id, 'keywords_s': searchkey, # 'searchkey_t': searchkey, 'url_s': keyword.url, -- cgit v1.2.3 From 64da5a5b37a7c016e863b2c3d615a21bdb45839a Mon Sep 17 00:00:00 2001 From: Mqdd Date: Wed, 10 Dec 2025 13:25:56 +0700 Subject: remove unused solr field --- indoteknik_custom/models/keywords.py | 1 - 1 file changed, 1 deletion(-) diff --git a/indoteknik_custom/models/keywords.py b/indoteknik_custom/models/keywords.py index cee189c4..03f5a11d 100644 --- a/indoteknik_custom/models/keywords.py +++ b/indoteknik_custom/models/keywords.py @@ -124,7 +124,6 @@ class Keywords(models.Model): try: doc = { 'id': keyword.id, - # 'type_s': 'keyword', 'category_id_i': keyword.category_id.id, 'keywords_s': searchkey, # 'searchkey_t': searchkey, -- cgit v1.2.3 From ed88e91aa769e07ba048fd8b3067442754c06065 Mon Sep 17 00:00:00 2001 From: Mqdd Date: Thu, 11 Dec 2025 08:53:01 +0700 Subject: disable query --- indoteknik_custom/models/keywords.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/keywords.py b/indoteknik_custom/models/keywords.py index 03f5a11d..5faa94a6 100644 --- a/indoteknik_custom/models/keywords.py +++ b/indoteknik_custom/models/keywords.py @@ -146,6 +146,6 @@ class Keywords(models.Model): def write(self, vals): result = super().write(vals) - if not self.env.context.get("skip_generate") and not self.skip: - self.generate_products() + # if not self.env.context.get("skip_generate") and not self.skip: + # self.generate_products() return result -- cgit v1.2.3 From 097f53b8cb0b2c39fb14f5f3a665410df2c7ebf3 Mon Sep 17 00:00:00 2001 From: Mqdd Date: Fri, 12 Dec 2025 08:55:07 +0700 Subject: done temp add product to keywords --- indoteknik_custom/models/product_template.py | 33 +++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py index 400d31e0..2cbef168 100755 --- a/indoteknik_custom/models/product_template.py +++ b/indoteknik_custom/models/product_template.py @@ -1,3 +1,4 @@ +from this import s from odoo import fields, models, api, tools, _ from datetime import datetime, timedelta, date from odoo.exceptions import UserError @@ -84,6 +85,7 @@ class ProductTemplate(models.Model): if self.env.user.id not in users_in_group.mapped('id') and active_model == None: raise UserError('Hanya MD yang bisa membuat Product') result = super(ProductTemplate, self).create(vals) + self.env['product.product']._add_product_to_keywords(result) return result # def write(self, values): @@ -883,6 +885,12 @@ class ProductTemplate(models.Model): result = super().write(vals) # Log changes self._log_field_changes_product(vals, old_values) + + # Add product to keywords + keyword_trigger = ['name', 'website_description', 'unpublished'] + if any(field in vals for field in keyword_trigger): + for product in self: + self.env['product.product']._add_product_to_keywords(product) return result # def write(self, vals): @@ -931,6 +939,24 @@ class ProductProduct(models.Model): barcode_box = fields.Char("Barcode Box") keyword_id = fields.Many2one('keywords', string='Keyword') + def _add_product_to_keywords(self,product): + keywords_model = self.env['keywords'] + if not product: + return False + + for product in self: + match_keywords = keywords_model.search([ + '|', + ('name', 'ilike', product.name), + ('keywords', 'ilike', product.website_description) + ]) + + for kw in match_keywords.filtered(lambda k: not k.skip): + if not self.unpublished and product.id not in kw.product_ids.ids: + kw.write({'product_ids': [(4, product.id)]}) + + return True + def generate_product_sla(self): product_variant_ids = self.env.context.get('active_ids', []) product_variant = self.search([('id', 'in', product_variant_ids)]) @@ -951,6 +977,7 @@ class ProductProduct(models.Model): if self.env.user.id not in users_in_group.mapped('id') and active_model == None: raise UserError('Hanya MD yang bisa membuat Product') result = super(ProductProduct, self).create(vals) + self._add_product_to_keywords(result) return result # def write(self, values): @@ -1315,7 +1342,11 @@ class ProductProduct(models.Model): 'public_categ_ids', 'search_rank', 'search_rank_weekly', 'image_1920', 'unpublished', 'image_carousel_lines' ] - + # pake ini kalau mau Cek semua field + # if vals: + trigger_fields = ['name', 'website_description', 'unpublished'] + if any(f in vals for f in trigger_fields): + self._add_product_to_keywords(vals) if any(field in vals for field in tracked_fields): old_values = self._collect_old_values(vals) result = super().write(vals) -- cgit v1.2.3 From c8fadfcb646ec86b73d92f199a98000aeabcbfe8 Mon Sep 17 00:00:00 2001 From: FIN-IT_AndriFP Date: Mon, 15 Dec 2025 11:06:20 +0700 Subject: (andri) comment create & write --- indoteknik_custom/models/keywords.py | 122 +++++++++++++++++------------------ 1 file changed, 61 insertions(+), 61 deletions(-) diff --git a/indoteknik_custom/models/keywords.py b/indoteknik_custom/models/keywords.py index 5faa94a6..28dfa09e 100644 --- a/indoteknik_custom/models/keywords.py +++ b/indoteknik_custom/models/keywords.py @@ -64,56 +64,56 @@ class Keywords(models.Model): # raise UserError("Tidak bisa create/write/duplicate karena keywords sudah dipakai") - def generate_products(self): - for record in self: - if not record.keywords: - continue - - keyword = "%" + record.keywords.strip() + "%" - - # Query dasar - sql = """ - SELECT pp.id - FROM product_product pp - JOIN product_template pt ON pt.id = pp.product_tmpl_id - LEFT JOIN product_public_category_product_template_rel rel - ON rel.product_template_id = pt.id - WHERE ( - pt.name ILIKE %s - OR pt.website_description ILIKE %s - AND pt.unpublished = False - AND pt.product_rating >= 8 - ) - """ - - params = [keyword, keyword] - - # Filter kategori ke child - if record.category_id: - child_categs = self.env['product.public.category'].search([ - ('id', 'child_of', record.category_id.id) - ]) - sql += " AND rel.product_public_category_id = ANY(%s)" - params.append(child_categs.ids) - - # Exec SQL - self.env.cr.execute(sql, params) - rows = self.env.cr.fetchall() - - product_ids = [r[0] for r in rows] - - if not product_ids: - raise UserError("Tidak berhasil menemukan barang") - - record.with_context(skip_generate=True).write({ - 'product_ids': [(6, 0, product_ids)] - }) - - _logger.info( - "Product Found: Found %s products for keyword '%s'", - len(product_ids), - record.keywords - ) + # def generate_products(self): + # for record in self: + # if not record.keywords: + # continue + + # keyword = "%" + record.keywords.strip() + "%" + + # # Query dasar + # sql = """ + # SELECT pp.id + # FROM product_product pp + # JOIN product_template pt ON pt.id = pp.product_tmpl_id + # LEFT JOIN product_public_category_product_template_rel rel + # ON rel.product_template_id = pt.id + # WHERE ( + # pt.name ILIKE %s + # OR pt.website_description ILIKE %s + # AND pt.unpublished = False + # AND pt.product_rating >= 8 + # ) + # """ + + # params = [keyword, keyword] + + # # Filter kategori ke child + # if record.category_id: + # child_categs = self.env['product.public.category'].search([ + # ('id', 'child_of', record.category_id.id) + # ]) + # sql += " AND rel.product_public_category_id = ANY(%s)" + # params.append(child_categs.ids) + + # # Exec SQL + # self.env.cr.execute(sql, params) + # rows = self.env.cr.fetchall() + + # product_ids = [r[0] for r in rows] + + # if not product_ids: + # raise UserError("Tidak berhasil menemukan barang") + + # record.with_context(skip_generate=True).write({ + # 'product_ids': [(6, 0, product_ids)] + # }) + + # _logger.info( + # "Product Found: Found %s products for keyword '%s'", + # len(product_ids), + # record.keywords + # ) def sync_solr(self): # solr_model = self.env['apache.solr'] @@ -138,14 +138,14 @@ class Keywords(models.Model): solr.add(documents) return True - @api.model - def create(self, vals): - record = super().create(vals) - record.generate_products() - return record - - def write(self, vals): - result = super().write(vals) - # if not self.env.context.get("skip_generate") and not self.skip: - # self.generate_products() - return result + # @api.model + # def create(self, vals): + # record = super().create(vals) + # # record.generate_products() + # return record + + # def write(self, vals): + # result = super().write(vals) + # # if not self.env.context.get("skip_generate") and not self.skip: + # # self.generate_products() + # return result -- cgit v1.2.3 From 0a81a44ccfdfc7219ee29b2b6f79fa73143e095d Mon Sep 17 00:00:00 2001 From: Mqdd Date: Mon, 15 Dec 2025 13:42:30 +0700 Subject: add button generate product manually --- indoteknik_custom/models/keywords.py | 5 ++++- indoteknik_custom/views/keywords.xml | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/indoteknik_custom/models/keywords.py b/indoteknik_custom/models/keywords.py index 5faa94a6..495a42cc 100644 --- a/indoteknik_custom/models/keywords.py +++ b/indoteknik_custom/models/keywords.py @@ -1,4 +1,5 @@ from itertools import product +from multiprocessing import Condition from odoo import fields, models, api, tools, _ import logging import re @@ -68,6 +69,8 @@ class Keywords(models.Model): for record in self: if not record.keywords: continue + if record.skip: + continue keyword = "%" + record.keywords.strip() + "%" @@ -141,7 +144,7 @@ class Keywords(models.Model): @api.model def create(self, vals): record = super().create(vals) - record.generate_products() + # record.generate_products() return record def write(self, vals): diff --git a/indoteknik_custom/views/keywords.xml b/indoteknik_custom/views/keywords.xml index f5374655..878952f8 100644 --- a/indoteknik_custom/views/keywords.xml +++ b/indoteknik_custom/views/keywords.xml @@ -19,6 +19,9 @@ keywords +
    +

    -- cgit v1.2.3 From 274a067754ae67bc0f7f77c4c079aa2634b8000c Mon Sep 17 00:00:00 2001 From: Mqdd Date: Mon, 15 Dec 2025 15:11:30 +0700 Subject: add clear button --- indoteknik_custom/models/keywords.py | 4 ++++ indoteknik_custom/views/keywords.xml | 1 + 2 files changed, 5 insertions(+) diff --git a/indoteknik_custom/models/keywords.py b/indoteknik_custom/models/keywords.py index 495a42cc..3602be15 100644 --- a/indoteknik_custom/models/keywords.py +++ b/indoteknik_custom/models/keywords.py @@ -65,6 +65,10 @@ class Keywords(models.Model): # raise UserError("Tidak bisa create/write/duplicate karena keywords sudah dipakai") + def clear_products(self): + for record in self: + record.product_ids = [(5, 0, 0)] + def generate_products(self): for record in self: if not record.keywords: diff --git a/indoteknik_custom/views/keywords.xml b/indoteknik_custom/views/keywords.xml index 878952f8..6f1a675f 100644 --- a/indoteknik_custom/views/keywords.xml +++ b/indoteknik_custom/views/keywords.xml @@ -21,6 +21,7 @@
    -- cgit v1.2.3 From dcfbed94e24f7bf6fd06bd0404c1424af2a840be Mon Sep 17 00:00:00 2001 From: Mqdd Date: Tue, 16 Dec 2025 09:39:32 +0700 Subject: fix quey --- indoteknik_custom/models/keywords.py | 38 ++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/indoteknik_custom/models/keywords.py b/indoteknik_custom/models/keywords.py index 3602be15..e0c7705c 100644 --- a/indoteknik_custom/models/keywords.py +++ b/indoteknik_custom/models/keywords.py @@ -71,31 +71,31 @@ class Keywords(models.Model): def generate_products(self): for record in self: - if not record.keywords: - continue - if record.skip: + if not record.keywords or record.skip: continue - keyword = "%" + record.keywords.strip() + "%" + keyword = f"%{record.keywords.strip()}%" - # Query dasar sql = """ - SELECT pp.id + SELECT DISTINCT pp.id FROM product_product pp JOIN product_template pt ON pt.id = pp.product_tmpl_id - LEFT JOIN product_public_category_product_template_rel rel + JOIN product_public_category_product_template_rel rel ON rel.product_template_id = pt.id - WHERE ( - pt.name ILIKE %s - OR pt.website_description ILIKE %s - AND pt.unpublished = False - AND pt.product_rating >= 8 - ) + WHERE + pp.active = true + AND COALESCE(pt.product_rating, 0) >= 8 + AND ( + pt.name ILIKE %s + OR COALESCE(pt.website_description, '') ILIKE %s + ) """ - params = [keyword, keyword] + params = [ + keyword, + keyword, + ] - # Filter kategori ke child if record.category_id: child_categs = self.env['product.public.category'].search([ ('id', 'child_of', record.category_id.id) @@ -103,14 +103,15 @@ class Keywords(models.Model): sql += " AND rel.product_public_category_id = ANY(%s)" params.append(child_categs.ids) - # Exec SQL self.env.cr.execute(sql, params) - rows = self.env.cr.fetchall() + rows = self.env.cr.fetchall() product_ids = [r[0] for r in rows] if not product_ids: - raise UserError("Tidak berhasil menemukan barang") + raise UserError( + f"Tidak berhasil menemukan barang untuk keyword '{record.keywords}'" + ) record.with_context(skip_generate=True).write({ 'product_ids': [(6, 0, product_ids)] @@ -121,7 +122,6 @@ class Keywords(models.Model): len(product_ids), record.keywords ) - def sync_solr(self): # solr_model = self.env['apache.solr'] documents = [] -- cgit v1.2.3 From 33e6fc960fc3dcdbb77eedbff6239b7763ace468 Mon Sep 17 00:00:00 2001 From: Mqdd Date: Thu, 18 Dec 2025 10:52:43 +0700 Subject: fix query --- indoteknik_custom/models/keywords.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/keywords.py b/indoteknik_custom/models/keywords.py index e0c7705c..69ecdd46 100644 --- a/indoteknik_custom/models/keywords.py +++ b/indoteknik_custom/models/keywords.py @@ -83,8 +83,8 @@ class Keywords(models.Model): JOIN product_public_category_product_template_rel rel ON rel.product_template_id = pt.id WHERE - pp.active = true - AND COALESCE(pt.product_rating, 0) >= 8 + COALESCE(pt.product_rating, 0) >= 3 + AND (pt.unpublished IS FALSE OR pt.unpublished IS NULL) AND ( pt.name ILIKE %s OR COALESCE(pt.website_description, '') ILIKE %s -- cgit v1.2.3 From fbb30fff2489546e4c21ab55d504b6b861e70deb Mon Sep 17 00:00:00 2001 From: Mqdd Date: Thu, 18 Dec 2025 15:43:02 +0700 Subject: fix solr sync --- indoteknik_custom/models/keywords.py | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/indoteknik_custom/models/keywords.py b/indoteknik_custom/models/keywords.py index 69ecdd46..225710ab 100644 --- a/indoteknik_custom/models/keywords.py +++ b/indoteknik_custom/models/keywords.py @@ -76,6 +76,7 @@ class Keywords(models.Model): keyword = f"%{record.keywords.strip()}%" + # AND (pt.unpublished IS FALSE OR pt.unpublished IS NULL) sql = """ SELECT DISTINCT pp.id FROM product_product pp @@ -83,11 +84,11 @@ class Keywords(models.Model): JOIN product_public_category_product_template_rel rel ON rel.product_template_id = pt.id WHERE - COALESCE(pt.product_rating, 0) >= 3 - AND (pt.unpublished IS FALSE OR pt.unpublished IS NULL) + pt.product_rating >= 3 + AND pp.active IS TRUE AND ( pt.name ILIKE %s - OR COALESCE(pt.website_description, '') ILIKE %s + OR pt.website_description ILIKE %s ) """ @@ -122,27 +123,34 @@ class Keywords(models.Model): len(product_ids), record.keywords ) + def sync_solr(self): - # solr_model = self.env['apache.solr'] + active_ids = self.env.context.get('active_ids', []) + if not active_ids: + _logger.warning("No active_ids found, nothing to sync") + return True + + keywords = self.browse(active_ids) + documents = [] - data = {} - for keyword in self.search([]): + for keyword in keywords: searchkey = (keyword.keywords or '').strip().lower().replace(' ', '-') try: doc = { 'id': keyword.id, 'category_id_i': keyword.category_id.id, 'keywords_s': searchkey, - # 'searchkey_t': searchkey, 'url_s': keyword.url, - 'product_ids_is': [p.product_tmpl_id.id for p in keyword.product_ids] + 'product_ids_is': [p.product_tmpl_id.id for p in keyword.product_ids], } documents.append(doc) - data = doc except Exception as e: _logger.error('failed %s', e) - _logger.error('doc data: %s', data) - solr.add(documents) + _logger.error('doc data: %s', doc) + + if documents: + solr.add(documents) + return True @api.model -- cgit v1.2.3 From 9596f3806d8f177c5a701c16be4b10c964be7c07 Mon Sep 17 00:00:00 2001 From: Mqdd Date: Thu, 18 Dec 2025 15:43:18 +0700 Subject: fix product rating --- indoteknik_custom/models/keywords.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/keywords.py b/indoteknik_custom/models/keywords.py index 225710ab..b2ada3f8 100644 --- a/indoteknik_custom/models/keywords.py +++ b/indoteknik_custom/models/keywords.py @@ -84,7 +84,7 @@ class Keywords(models.Model): JOIN product_public_category_product_template_rel rel ON rel.product_template_id = pt.id WHERE - pt.product_rating >= 3 + pt.product_rating >= 8 AND pp.active IS TRUE AND ( pt.name ILIKE %s -- cgit v1.2.3 From 41e5e8defc0d0b928e43307f72887b0789ffb589 Mon Sep 17 00:00:00 2001 From: Mqdd Date: Thu, 18 Dec 2025 16:00:19 +0700 Subject: fix duplicate keywords functions --- indoteknik_custom/models/keywords.py | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/indoteknik_custom/models/keywords.py b/indoteknik_custom/models/keywords.py index b2ada3f8..41deea3e 100644 --- a/indoteknik_custom/models/keywords.py +++ b/indoteknik_custom/models/keywords.py @@ -8,7 +8,6 @@ from odoo.exceptions import UserError import base64 import xlrd, xlwt import io -from bs4 import BeautifulSoup _logger = logging.getLogger(__name__) @@ -19,7 +18,7 @@ class Keywords(models.Model): _name = 'keywords' _order= 'id desc' - category_id = fields.Many2one('product.public.category', string='Category', required=True) + category_id = fields.Many2one('product.public.category', string='Category', required=True, help="Category to filter products when generating products for this keyword and to throw to solr") keywords = fields.Char('Keywords', required=True) product_ids = fields.Many2many( 'product.product', @@ -29,9 +28,9 @@ class Keywords(models.Model): string='Products' ) name = fields.Char('Name', compute="_compute_name") - skip = fields.Boolean('Skip Generate Product', default=False) - url = fields.Char('Website URL', compute="_compute_url") - sum = fields.Integer('Total Product', compute="_compute_total_product", readonly=True) + skip = fields.Boolean('Skip Generate Product', default=False, help="If checked, the system will not generate products for this keyword") + url = fields.Char('Website URL', compute="_compute_url", help="Generated website url based on keywords") + sum = fields.Integer('Total Product', compute="_compute_total_product", readonly=True, help="Total products found for this keyword including variants") @api.depends('product_ids') def _compute_total_product(self): @@ -54,15 +53,19 @@ class Keywords(models.Model): if not record.name: record.name = record.keywords - # @api.constrains('keywords') - # def check_already_exist(self): - # model = self.env['keywords'] - # for record in self: - # match = model.search([ - # ('name', 'ilike', record.name) - # ]) - # if match: - # raise UserError("Tidak bisa create/write/duplicate karena keywords sudah dipakai") + @api.constrains('keywords', 'name') + def check_already_exist(self): + model = self.env['keywords'] + for record in self: + match = model.search([ '|', + ('name', '=', record.name), + ('keywords', '=', record.keywords) + ]) + if match: + raise UserError("Tidak bisa create/write/duplicate karena keywords sudah dipakai") + + def copy(self): + raise UserError("Duplicate Record not allowed") def clear_products(self): -- cgit v1.2.3 From 2e3e5853ce9fc3e9568eea3a673c2a154b8de9da Mon Sep 17 00:00:00 2001 From: Mqdd Date: Fri, 19 Dec 2025 15:10:58 +0700 Subject: fix duplicate keywords --- indoteknik_custom/models/keywords.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/indoteknik_custom/models/keywords.py b/indoteknik_custom/models/keywords.py index 41deea3e..fedaa180 100644 --- a/indoteknik_custom/models/keywords.py +++ b/indoteknik_custom/models/keywords.py @@ -53,16 +53,15 @@ class Keywords(models.Model): if not record.name: record.name = record.keywords - @api.constrains('keywords', 'name') + @api.depends('keywords') def check_already_exist(self): model = self.env['keywords'] for record in self: - match = model.search([ '|', - ('name', '=', record.name), - ('keywords', '=', record.keywords) + match = model.search([ + ('keywords', 'ilike', record.keywords) ]) if match: - raise UserError("Tidak bisa create/write/duplicate karena keywords sudah dipakai") + raise UserError("Tidak bisa create karena keywords sudah dipakai") def copy(self): raise UserError("Duplicate Record not allowed") @@ -159,11 +158,13 @@ class Keywords(models.Model): @api.model def create(self, vals): record = super().create(vals) + self.check_already_exist() # record.generate_products() return record def write(self, vals): result = super().write(vals) + self.check_already_exist() # if not self.env.context.get("skip_generate") and not self.skip: # self.generate_products() return result -- cgit v1.2.3 From b0c16534780cc69320f16e7d3bbec29a811a6215 Mon Sep 17 00:00:00 2001 From: Mqdd Date: Tue, 30 Dec 2025 11:48:16 +0700 Subject: fix check duplicate --- indoteknik_custom/models/keywords.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/keywords.py b/indoteknik_custom/models/keywords.py index fedaa180..47546c1f 100644 --- a/indoteknik_custom/models/keywords.py +++ b/indoteknik_custom/models/keywords.py @@ -53,12 +53,12 @@ class Keywords(models.Model): if not record.name: record.name = record.keywords - @api.depends('keywords') def check_already_exist(self): model = self.env['keywords'] for record in self: match = model.search([ - ('keywords', 'ilike', record.keywords) + ('keywords', '=', record.keywords), + ('category_id.id', '=', record.category_id.id) ]) if match: raise UserError("Tidak bisa create karena keywords sudah dipakai") -- cgit v1.2.3 From ea309ccb34ed588e91198cde98074c77ed1b2bef Mon Sep 17 00:00:00 2001 From: Mqdd Date: Tue, 30 Dec 2025 16:19:05 +0700 Subject: add brands --- indoteknik_custom/models/keywords.py | 26 ++++--- indoteknik_custom/views/keywords.xml | 140 ++++++++++++++++++----------------- 2 files changed, 87 insertions(+), 79 deletions(-) diff --git a/indoteknik_custom/models/keywords.py b/indoteknik_custom/models/keywords.py index 47546c1f..ef3715c9 100644 --- a/indoteknik_custom/models/keywords.py +++ b/indoteknik_custom/models/keywords.py @@ -20,6 +20,10 @@ class Keywords(models.Model): category_id = fields.Many2one('product.public.category', string='Category', required=True, help="Category to filter products when generating products for this keyword and to throw to solr") keywords = fields.Char('Keywords', required=True) + brand_id = fields.Many2one( + comodel_name="x_manufactures", + string="Brand" + ) product_ids = fields.Many2many( 'product.product', 'keywords_product_rel', @@ -53,15 +57,15 @@ class Keywords(models.Model): if not record.name: record.name = record.keywords - def check_already_exist(self): - model = self.env['keywords'] - for record in self: - match = model.search([ - ('keywords', '=', record.keywords), - ('category_id.id', '=', record.category_id.id) - ]) - if match: - raise UserError("Tidak bisa create karena keywords sudah dipakai") + # def check_already_exist(self): + # model = self.env['keywords'] + # for record in self: + # match = model.search([ + # ('keywords', '=', record.keywords), + # ('category_id.id', '=', record.category_id.id) + # ]) + # if match: + # raise UserError("Tidak bisa create karena keywords sudah dipakai") def copy(self): raise UserError("Duplicate Record not allowed") @@ -158,13 +162,13 @@ class Keywords(models.Model): @api.model def create(self, vals): record = super().create(vals) - self.check_already_exist() + # self.check_already_exist() # record.generate_products() return record def write(self, vals): result = super().write(vals) - self.check_already_exist() + # self.check_already_exist() # if not self.env.context.get("skip_generate") and not self.skip: # self.generate_products() return result diff --git a/indoteknik_custom/views/keywords.xml b/indoteknik_custom/views/keywords.xml index 6f1a675f..febd6d39 100644 --- a/indoteknik_custom/views/keywords.xml +++ b/indoteknik_custom/views/keywords.xml @@ -1,77 +1,81 @@ - - keywords.tree - keywords - - - - - - - - - - + + keywords.tree + keywords + + + + + + + + + + + - + keywords.form keywords - -
    -
    - -
    -

    - -

    -
    - - - - - - - - -
    - -
    -
    +
    +
    +
    + +
    +

    + +

    +
    + + + + + + + + + +
    +
    + +
    - - keywords.list.select - keywords - - - - - - - - - - - Keywords - ir.actions.act_window - keywords - - tree,form - + + keywords.list.select + keywords + + + + + + + + + + + + + Keywords + ir.actions.act_window + keywords + + tree,form + - - Sync to solr - - - code - model.sync_solr() - - + + Sync to solr + + + code + model.sync_solr() + +
    -- cgit v1.2.3 From a069b9d6b986e514c17b42df5e807b7076338cda Mon Sep 17 00:00:00 2001 From: Mqdd Date: Wed, 31 Dec 2025 13:29:51 +0700 Subject: add brand & compute url --- indoteknik_custom/models/keywords.py | 92 +++++++++++++++++++++++++++--------- 1 file changed, 70 insertions(+), 22 deletions(-) diff --git a/indoteknik_custom/models/keywords.py b/indoteknik_custom/models/keywords.py index ef3715c9..53cf4225 100644 --- a/indoteknik_custom/models/keywords.py +++ b/indoteknik_custom/models/keywords.py @@ -41,16 +41,25 @@ class Keywords(models.Model): for record in self: record.sum = len(record.product_ids) - @api.depends('keywords') + + @api.depends('keywords', 'brand_id') def _compute_url(self): prefix = "https://indoteknik.com/searchkey/" for record in self: - if record.keywords: - slug = re.sub(r'[^a-zA-Z0-9]+', '-', record.keywords.strip().lower()) - slug = slug.strip('-') - record.url = prefix + slug - else: + if not record.keywords: record.url = False + continue + slug = re.sub(r'[^a-zA-Z0-9]+', '-', record.keywords.strip().lower()).strip('-') + + if record.brand_id: + brand_slug = re.sub( + r'[^a-zA-Z0-9]+', + '-', + record.brand_id.x_name.strip().lower() + ).strip('-') + record.url = f"{prefix}{slug}-{brand_slug}" + else: + record.url = f"{prefix}{slug}" def _compute_name(self): for record in self: @@ -75,14 +84,23 @@ class Keywords(models.Model): for record in self: record.product_ids = [(5, 0, 0)] + def generate_products(self): for record in self: - if not record.keywords or record.skip: + if record.skip: continue - keyword = f"%{record.keywords.strip()}%" + if not record.keywords: + raise UserError("Keyword wajib diisi") + + if not record.category_id: + raise UserError( + f"Category wajib diisi untuk keyword '{record.keywords}'" + ) + + keyword_raw = record.keywords.strip() + keyword = f"%{keyword_raw}%" - # AND (pt.unpublished IS FALSE OR pt.unpublished IS NULL) sql = """ SELECT DISTINCT pp.id FROM product_product pp @@ -98,26 +116,55 @@ class Keywords(models.Model): ) """ - params = [ - keyword, - keyword, - ] + params = [keyword, keyword] + + # ====================== + # CATEGORY (WAJIB) + # ====================== + child_categs = self.env['product.public.category'].search([ + ('id', 'child_of', record.category_id.id) + ]) + + sql += " AND rel.product_public_category_id = ANY(%s)" + params.append(child_categs.ids) - if record.category_id: - child_categs = self.env['product.public.category'].search([ - ('id', 'child_of', record.category_id.id) + # ====================== + # BRAND (OPTIONAL) + # ====================== + if record.brand_id: + brand = record.brand_id.x_name.strip() + + brand_kw_1 = f"%{brand} {keyword_raw}%" + brand_kw_2 = f"%{keyword_raw} {brand}%" + + sql += """ + AND ( + pt.name ILIKE %s + OR pt.name ILIKE %s + OR pt.website_description ILIKE %s + OR pt.website_description ILIKE %s + ) + """ + + params.extend([ + brand_kw_1, + brand_kw_2, + brand_kw_1, + brand_kw_2, ]) - sql += " AND rel.product_public_category_id = ANY(%s)" - params.append(child_categs.ids) + # ====================== + # EXECUTE + # ====================== self.env.cr.execute(sql, params) - rows = self.env.cr.fetchall() + product_ids = [r[0] for r in rows] if not product_ids: raise UserError( - f"Tidak berhasil menemukan barang untuk keyword '{record.keywords}'" + f"Tidak menemukan product untuk keyword " + f"'{record.keywords}' di category '{record.category_id.name}'" ) record.with_context(skip_generate=True).write({ @@ -125,9 +172,10 @@ class Keywords(models.Model): }) _logger.info( - "Product Found: Found %s products for keyword '%s'", + "Product Found: %s products | keyword='%s' | category='%s'", len(product_ids), - record.keywords + record.keywords, + record.category_id.name ) def sync_solr(self): -- cgit v1.2.3 From 2d46030560670a0d016012465447ea37b0242581 Mon Sep 17 00:00:00 2001 From: Mqdd Date: Wed, 31 Dec 2025 13:57:38 +0700 Subject: cannot sync solr when no products --- indoteknik_custom/models/keywords.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/indoteknik_custom/models/keywords.py b/indoteknik_custom/models/keywords.py index 53cf4225..d7cfc810 100644 --- a/indoteknik_custom/models/keywords.py +++ b/indoteknik_custom/models/keywords.py @@ -132,7 +132,7 @@ class Keywords(models.Model): # BRAND (OPTIONAL) # ====================== if record.brand_id: - brand = record.brand_id.x_name.strip() + brand = record.brand_id.x_name brand_kw_1 = f"%{brand} {keyword_raw}%" brand_kw_2 = f"%{keyword_raw} {brand}%" @@ -183,12 +183,16 @@ class Keywords(models.Model): if not active_ids: _logger.warning("No active_ids found, nothing to sync") return True + elif active_ids and self.sum < 1: + raise UserError("Tidak ada product untuk keyword ini, sync ke solr dibatalkan") keywords = self.browse(active_ids) documents = [] for keyword in keywords: searchkey = (keyword.keywords or '').strip().lower().replace(' ', '-') + if keyword.brand_id: + searchkey = f"{searchkey}-{keyword.brand_id.x_name.strip().lower().replace(' ', '-')}" try: doc = { 'id': keyword.id, -- cgit v1.2.3 From 604598292fa99546b34422613a8f6399e1a55c61 Mon Sep 17 00:00:00 2001 From: Mqdd Date: Wed, 31 Dec 2025 21:54:08 +0700 Subject: fix check duplicate --- indoteknik_custom/models/keywords.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/indoteknik_custom/models/keywords.py b/indoteknik_custom/models/keywords.py index d7cfc810..f6c1cf6e 100644 --- a/indoteknik_custom/models/keywords.py +++ b/indoteknik_custom/models/keywords.py @@ -66,15 +66,18 @@ class Keywords(models.Model): if not record.name: record.name = record.keywords - # def check_already_exist(self): - # model = self.env['keywords'] - # for record in self: - # match = model.search([ - # ('keywords', '=', record.keywords), - # ('category_id.id', '=', record.category_id.id) - # ]) - # if match: - # raise UserError("Tidak bisa create karena keywords sudah dipakai") + @api.constrains('keywords', 'category_id') + def check_already_exist(self): + for record in self: + if not (record.keywords and record.category_id): + continue + match = self.search([ + ('id', '!=', record.id), + ('keywords', '=', record.keywords), + ('category_id', '=', record.category_id.id) + ], limit = 1) + if match: + raise UserError("Tidak bisa create karena keywords sudah dipakai") def copy(self): raise UserError("Duplicate Record not allowed") -- cgit v1.2.3 From 9039bb8de5ea157692120b8b2dd24338a13a0071 Mon Sep 17 00:00:00 2001 From: Mqdd Date: Wed, 31 Dec 2025 22:24:35 +0700 Subject: rev query (need improvement) --- indoteknik_custom/models/keywords.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/indoteknik_custom/models/keywords.py b/indoteknik_custom/models/keywords.py index f6c1cf6e..b5c7ae8c 100644 --- a/indoteknik_custom/models/keywords.py +++ b/indoteknik_custom/models/keywords.py @@ -139,6 +139,8 @@ class Keywords(models.Model): brand_kw_1 = f"%{brand} {keyword_raw}%" brand_kw_2 = f"%{keyword_raw} {brand}%" + keyword = f"%{keyword_raw}%" + sql += "AND pt.x_manufacture = %s" sql += """ AND ( @@ -147,13 +149,22 @@ class Keywords(models.Model): OR pt.website_description ILIKE %s OR pt.website_description ILIKE %s ) + OR ( + pt.x_manufacture = %s + AND (pt.name ilike %s + OR pt.website_description ilike %s) + ) """ params.extend([ + record.brand_id.id, brand_kw_1, brand_kw_2, brand_kw_1, brand_kw_2, + record.brand_id.id, + keyword, + keyword ]) # ====================== -- cgit v1.2.3 From 6062eff60486c37144e2672922ad7a5d8333d5f4 Mon Sep 17 00:00:00 2001 From: Mqdd Date: Tue, 17 Feb 2026 16:42:40 +0700 Subject: fix merge --- indoteknik_custom/__manifest__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py index 262096e4..ca23995c 100755 --- a/indoteknik_custom/__manifest__.py +++ b/indoteknik_custom/__manifest__.py @@ -195,7 +195,6 @@ 'views/uom_uom.xml', 'views/update_depreciation_move_wizard_view.xml', 'views/commission_internal.xml', - 'views/update_depreciation_move_wizard_view.xml', 'views/keywords.xml' ], 'demo': [], -- cgit v1.2.3 From 2571d2d4d14c843f65e552fa93726b1f0810f42d Mon Sep 17 00:00:00 2001 From: Mqdd Date: Tue, 17 Feb 2026 16:44:37 +0700 Subject: remove unused views --- indoteknik_custom/__manifest__.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py index ca23995c..36588967 100755 --- a/indoteknik_custom/__manifest__.py +++ b/indoteknik_custom/__manifest__.py @@ -178,17 +178,13 @@ 'views/refund_sale_order.xml', 'views/advance_payment_request.xml', 'views/advance_payment_settlement.xml', - # 'views/refund_sale_order.xml', 'views/tukar_guling.xml', - # 'views/tukar_guling_return_views.xml' 'views/tukar_guling_po.xml', - # 'views/refund_sale_order.xml', 'views/update_date_planned_po_wizard_view.xml', 'views/unpaid_invoice_view.xml', 'views/letter_receivable.xml', 'views/letter_receivable_mail_template.xml', 'views/mail_template_pum.xml', - # 'views/reimburse.xml', 'views/sj_tele.xml', 'views/close_tempo_mail_template.xml', 'views/domain_apo.xml', -- cgit v1.2.3 From 48d95b4e9f11eac11931a4679b0cc5cc3249e69d Mon Sep 17 00:00:00 2001 From: Mqdd Date: Wed, 18 Feb 2026 13:06:30 +0700 Subject: remove brand --- indoteknik_custom/models/keywords.py | 118 ++++++++--------------------------- 1 file changed, 27 insertions(+), 91 deletions(-) diff --git a/indoteknik_custom/models/keywords.py b/indoteknik_custom/models/keywords.py index b5c7ae8c..f4774bbd 100644 --- a/indoteknik_custom/models/keywords.py +++ b/indoteknik_custom/models/keywords.py @@ -41,25 +41,16 @@ class Keywords(models.Model): for record in self: record.sum = len(record.product_ids) - - @api.depends('keywords', 'brand_id') + @api.depends('keywords') def _compute_url(self): prefix = "https://indoteknik.com/searchkey/" for record in self: - if not record.keywords: - record.url = False - continue - slug = re.sub(r'[^a-zA-Z0-9]+', '-', record.keywords.strip().lower()).strip('-') - - if record.brand_id: - brand_slug = re.sub( - r'[^a-zA-Z0-9]+', - '-', - record.brand_id.x_name.strip().lower() - ).strip('-') - record.url = f"{prefix}{slug}-{brand_slug}" + if record.keywords: + slug = re.sub(r'[^a-zA-Z0-9]+', '-', record.keywords.strip().lower()) + slug = slug.strip('-') + record.url = prefix + slug else: - record.url = f"{prefix}{slug}" + record.url = False def _compute_name(self): for record in self: @@ -68,14 +59,13 @@ class Keywords(models.Model): @api.constrains('keywords', 'category_id') def check_already_exist(self): + model = self.env['keywords'] for record in self: - if not (record.keywords and record.category_id): - continue - match = self.search([ - ('id', '!=', record.id), + match = model.search([ ('keywords', '=', record.keywords), - ('category_id', '=', record.category_id.id) - ], limit = 1) + ('category_id.id', '=', record.category_id.id), + ('id', '!=', record.id) + ]) if match: raise UserError("Tidak bisa create karena keywords sudah dipakai") @@ -87,23 +77,14 @@ class Keywords(models.Model): for record in self: record.product_ids = [(5, 0, 0)] - def generate_products(self): for record in self: - if record.skip: + if not record.keywords or record.skip: continue - if not record.keywords: - raise UserError("Keyword wajib diisi") - - if not record.category_id: - raise UserError( - f"Category wajib diisi untuk keyword '{record.keywords}'" - ) - - keyword_raw = record.keywords.strip() - keyword = f"%{keyword_raw}%" + keyword = f"%{record.keywords.strip()}%" + # AND (pt.unpublished IS FALSE OR pt.unpublished IS NULL) sql = """ SELECT DISTINCT pp.id FROM product_product pp @@ -119,66 +100,26 @@ class Keywords(models.Model): ) """ - params = [keyword, keyword] - - # ====================== - # CATEGORY (WAJIB) - # ====================== - child_categs = self.env['product.public.category'].search([ - ('id', 'child_of', record.category_id.id) - ]) - - sql += " AND rel.product_public_category_id = ANY(%s)" - params.append(child_categs.ids) - - # ====================== - # BRAND (OPTIONAL) - # ====================== - if record.brand_id: - brand = record.brand_id.x_name + params = [ + keyword, + keyword, + ] - brand_kw_1 = f"%{brand} {keyword_raw}%" - brand_kw_2 = f"%{keyword_raw} {brand}%" - keyword = f"%{keyword_raw}%" - sql += "AND pt.x_manufacture = %s" - - sql += """ - AND ( - pt.name ILIKE %s - OR pt.name ILIKE %s - OR pt.website_description ILIKE %s - OR pt.website_description ILIKE %s - ) - OR ( - pt.x_manufacture = %s - AND (pt.name ilike %s - OR pt.website_description ilike %s) - ) - """ - - params.extend([ - record.brand_id.id, - brand_kw_1, - brand_kw_2, - brand_kw_1, - brand_kw_2, - record.brand_id.id, - keyword, - keyword + if record.category_id: + child_categs = self.env['product.public.category'].search([ + ('id', 'child_of', record.category_id.id) ]) + sql += " AND rel.product_public_category_id = ANY(%s)" + params.append(child_categs.ids) - # ====================== - # EXECUTE - # ====================== self.env.cr.execute(sql, params) - rows = self.env.cr.fetchall() + rows = self.env.cr.fetchall() product_ids = [r[0] for r in rows] if not product_ids: raise UserError( - f"Tidak menemukan product untuk keyword " - f"'{record.keywords}' di category '{record.category_id.name}'" + f"Tidak berhasil menemukan barang untuk keyword '{record.keywords}'" ) record.with_context(skip_generate=True).write({ @@ -186,10 +127,9 @@ class Keywords(models.Model): }) _logger.info( - "Product Found: %s products | keyword='%s' | category='%s'", + "Product Found: Found %s products for keyword '%s'", len(product_ids), - record.keywords, - record.category_id.name + record.keywords ) def sync_solr(self): @@ -197,16 +137,12 @@ class Keywords(models.Model): if not active_ids: _logger.warning("No active_ids found, nothing to sync") return True - elif active_ids and self.sum < 1: - raise UserError("Tidak ada product untuk keyword ini, sync ke solr dibatalkan") keywords = self.browse(active_ids) documents = [] for keyword in keywords: searchkey = (keyword.keywords or '').strip().lower().replace(' ', '-') - if keyword.brand_id: - searchkey = f"{searchkey}-{keyword.brand_id.x_name.strip().lower().replace(' ', '-')}" try: doc = { 'id': keyword.id, -- cgit v1.2.3 From e419a0e52ac9d01ac0b8900bfb2bebff1db522c9 Mon Sep 17 00:00:00 2001 From: Mqdd Date: Wed, 18 Feb 2026 14:45:39 +0700 Subject: remove brand_Id --- indoteknik_custom/models/keywords.py | 4 ---- indoteknik_custom/views/keywords.xml | 3 --- 2 files changed, 7 deletions(-) diff --git a/indoteknik_custom/models/keywords.py b/indoteknik_custom/models/keywords.py index f4774bbd..20a18726 100644 --- a/indoteknik_custom/models/keywords.py +++ b/indoteknik_custom/models/keywords.py @@ -20,10 +20,6 @@ class Keywords(models.Model): category_id = fields.Many2one('product.public.category', string='Category', required=True, help="Category to filter products when generating products for this keyword and to throw to solr") keywords = fields.Char('Keywords', required=True) - brand_id = fields.Many2one( - comodel_name="x_manufactures", - string="Brand" - ) product_ids = fields.Many2many( 'product.product', 'keywords_product_rel', diff --git a/indoteknik_custom/views/keywords.xml b/indoteknik_custom/views/keywords.xml index febd6d39..9faa7112 100644 --- a/indoteknik_custom/views/keywords.xml +++ b/indoteknik_custom/views/keywords.xml @@ -6,7 +6,6 @@ - @@ -32,7 +31,6 @@
    - @@ -51,7 +49,6 @@ - -- cgit v1.2.3 From d098ff60225cd6d7770a08bdd1c06aafdd74f1d9 Mon Sep 17 00:00:00 2001 From: Mqdd Date: Thu, 19 Feb 2026 15:44:17 +0700 Subject: sync to solr --- indoteknik_custom/models/keywords.py | 66 ++++++++++++++++++++++++++++++++++-- indoteknik_custom/views/keywords.xml | 18 ++++++++++ 2 files changed, 81 insertions(+), 3 deletions(-) diff --git a/indoteknik_custom/models/keywords.py b/indoteknik_custom/models/keywords.py index 20a18726..ca5fc8d0 100644 --- a/indoteknik_custom/models/keywords.py +++ b/indoteknik_custom/models/keywords.py @@ -31,6 +31,7 @@ class Keywords(models.Model): skip = fields.Boolean('Skip Generate Product', default=False, help="If checked, the system will not generate products for this keyword") url = fields.Char('Website URL', compute="_compute_url", help="Generated website url based on keywords") sum = fields.Integer('Total Product', compute="_compute_total_product", readonly=True, help="Total products found for this keyword including variants") + solr_flag = fields.Integer(string='Solr Flag', default=0, help="0=no sync needed, 2=needs sync, 1=queued") @api.depends('product_ids') def _compute_total_product(self): @@ -128,7 +129,62 @@ class Keywords(models.Model): record.keywords ) + @api.onchange('keywords', 'category_id', 'product_ids') + def _onchange_solr_flag(self): + """Set solr_flag=2 when tracked fields change to trigger queue sync""" + for record in self: + if not record.skip: + record.solr_flag = 2 + + def solr_flag_to_queue(self, limit=500): + """Find keywords with solr_flag=2 and create apache.solr.queue entries""" + keywords = self.search([('solr_flag', '=', 2)], limit=limit) + + for keyword in keywords: + # Create unique queue entry + queue_obj = self.env['apache.solr.queue'] + queue_obj.create_unique({ + 'res_model': 'keywords', + 'res_id': keyword.id, + 'function_name': '_sync_keywords_queue_callback' + }) + + # Set flag to indicate queued + keyword.solr_flag = 1 + + if keywords: + _logger.info( + 'Queued %s keywords for Solr synchronization', + len(keywords) + ) + + return True + + def _sync_keywords_queue_callback(self): + """Callback method executed by apache.solr.queue - syncs keyword data to Solr""" + documents = [] + for keyword in self: + searchkey = (keyword.keywords or '').strip().lower().replace(' ', '-') + try: + doc = { + 'id': keyword.id, + 'category_id_i': keyword.category_id.id, + 'keywords_s': searchkey, + 'url_s': keyword.url, + 'product_ids_is': [p.product_tmpl_id.id for p in keyword.product_ids], + } + documents.append(doc) + except Exception as e: + _logger.error('failed %s', e) + _logger.error('doc data: %s', doc) + + if documents: + solr.add(documents) + + return True + def sync_solr(self): + """Manual sync method for active_ids context (backward compatibility)""" active_ids = self.env.context.get('active_ids', []) if not active_ids: _logger.warning("No active_ids found, nothing to sync") @@ -166,7 +222,11 @@ class Keywords(models.Model): def write(self, vals): result = super().write(vals) - # self.check_already_exist() - # if not self.env.context.get("skip_generate") and not self.skip: - # self.generate_products() + + tracked_fields = ['keywords', 'category_id', 'product_ids'] + neded_sync = any(field in vals for field in tracked_fields) + if neded_sync and self.skip == False: + for record in self: + record.solr_flag = 2 + return result diff --git a/indoteknik_custom/views/keywords.xml b/indoteknik_custom/views/keywords.xml index 9faa7112..a33edae0 100644 --- a/indoteknik_custom/views/keywords.xml +++ b/indoteknik_custom/views/keywords.xml @@ -9,6 +9,7 @@ + @@ -36,6 +37,7 @@ +
    @@ -51,6 +53,7 @@ + @@ -75,4 +78,19 @@ parent="website_sale.menu_orders" action="action_keywords" sequence="100"/> + + + + Sync Keywords To Solr: Queue Solr Flag 2 + 1 + hours + -1 + + + model.solr_flag_to_queue(limit=500) + code + 55 + False + + -- cgit v1.2.3 From 605241e33856c45660fce9ba16102557160c235a Mon Sep 17 00:00:00 2001 From: Mqdd Date: Thu, 19 Feb 2026 15:44:47 +0700 Subject: sync solr to production --- indoteknik_custom/models/keywords.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/keywords.py b/indoteknik_custom/models/keywords.py index ca5fc8d0..4ab649dc 100644 --- a/indoteknik_custom/models/keywords.py +++ b/indoteknik_custom/models/keywords.py @@ -11,8 +11,8 @@ import io _logger = logging.getLogger(__name__) -# solr = pysolr.Solr('http://10.148.0.5:8983/solr/searchkey/', always_commit=True, timeout=30) -solr = pysolr.Solr('http://127.0.0.1:8983/solr/searchkey/', always_commit=True, timeout=30) +solr = pysolr.Solr('http://10.148.0.5:8983/solr/searchkey/', always_commit=True, timeout=30) +# solr = pysolr.Solr('http://127.0.0.1:8983/solr/searchkey/', always_commit=True, timeout=30) class Keywords(models.Model): _name = 'keywords' -- cgit v1.2.3