diff options
| author | Mqdd <ahmadmiqdad27@gmail.com> | 2026-02-24 10:06:40 +0700 |
|---|---|---|
| committer | Mqdd <ahmadmiqdad27@gmail.com> | 2026-02-24 10:06:40 +0700 |
| commit | 41bafc2338c260cd5382dad7f73666697b46e345 (patch) | |
| tree | 1183b80bf4c6e44b4ac6689ea80b86e263a38f26 | |
| parent | c08d16f53c4e2c97e74f33018e00efabd08664b3 (diff) | |
| parent | 0b64d465d109392cdb4e634b1ccfffa56935d5e5 (diff) | |
Merge branch 'odoo-backup' of https://bitbucket.org/altafixco/indoteknik-addons into gudang-service
| -rwxr-xr-x | indoteknik_custom/__manifest__.py | 11 | ||||
| -rwxr-xr-x | indoteknik_custom/models/__init__.py | 3 | ||||
| -rw-r--r-- | indoteknik_custom/models/advance_payment_request.py | 96 | ||||
| -rw-r--r-- | indoteknik_custom/models/kartu_stock.py | 186 | ||||
| -rw-r--r-- | indoteknik_custom/models/keywords.py | 253 | ||||
| -rw-r--r-- | indoteknik_custom/models/price_group.py | 20 | ||||
| -rwxr-xr-x | indoteknik_custom/models/product_template.py | 175 | ||||
| -rwxr-xr-x | indoteknik_custom/models/purchase_pricelist.py | 7 | ||||
| -rwxr-xr-x | indoteknik_custom/models/sale_order.py | 2 | ||||
| -rw-r--r-- | indoteknik_custom/models/shipment_group.py | 2 | ||||
| -rw-r--r-- | indoteknik_custom/models/stock_inventory.py | 14 | ||||
| -rwxr-xr-x | indoteknik_custom/security/ir.model.access.csv | 3 | ||||
| -rw-r--r-- | indoteknik_custom/views/account_move_line.xml | 3 | ||||
| -rw-r--r-- | indoteknik_custom/views/advance_payment_request.xml | 24 | ||||
| -rw-r--r-- | indoteknik_custom/views/advance_payment_settlement.xml | 14 | ||||
| -rw-r--r-- | indoteknik_custom/views/find_page.xml | 13 | ||||
| -rw-r--r-- | indoteknik_custom/views/kartu_stock.xml | 17 | ||||
| -rw-r--r-- | indoteknik_custom/views/keywords.xml | 96 |
18 files changed, 824 insertions, 115 deletions
diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py index fd6f3140..2a7d8ad0 100755 --- a/indoteknik_custom/__manifest__.py +++ b/indoteknik_custom/__manifest__.py @@ -8,7 +8,7 @@ 'author': 'Rafi Zadanly', 'website': '', 'images': ['assets/favicon.ico'], - 'depends': ['base', 'coupon', 'delivery', 'sale', 'sale_management', 'vit_kelurahan', 'vit_efaktur'], + 'depends': ['base', 'coupon', 'delivery', 'sale', 'sale_management', 'vit_kelurahan', 'vit_efaktur', 'proweb_kartu_stok'], 'data': [ 'views/assets.xml', 'security/ir.model.access.csv', @@ -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', @@ -178,22 +178,21 @@ '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', 'views/uom_uom.xml', + 'views/update_depreciation_move_wizard_view.xml', 'views/commission_internal.xml', + 'views/keywords.xml', + 'views/kartu_stock.xml', 'views/update_depreciation_move_wizard_view.xml', 'views/gudang_service.xml' ], diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py index 4e5c3d3b..1737e0ef 100755 --- a/indoteknik_custom/models/__init__.py +++ b/indoteknik_custom/models/__init__.py @@ -167,3 +167,6 @@ from . import uom_uom from . import commission_internal from . import gudang_service from . import update_depreciation_move_wizard +from . import keywords +from . import kartu_stock + diff --git a/indoteknik_custom/models/advance_payment_request.py b/indoteknik_custom/models/advance_payment_request.py index 8cadb1b6..039d18a5 100644 --- a/indoteknik_custom/models/advance_payment_request.py +++ b/indoteknik_custom/models/advance_payment_request.py @@ -155,6 +155,18 @@ class AdvancePaymentRequest(models.Model): compute='_compute_is_current_user_ap' ) + estimate_line_ids = fields.One2many('advance.payment.request.estimate.line', 'request_id', string='Rincian Estimasi') + + @api.constrains('nominal', 'estimate_line_ids') + def _check_nominal_vs_estimate_total(self): + for rec in self: + if rec.type_request == 'pum': + if not rec.estimate_line_ids: + raise UserError("Rincian estimasi wajib diisi untuk PUM. Silakan tambahkan rincian estimasi.") + total_estimate = sum(line.nominal for line in rec.estimate_line_ids) + if round(total_estimate, 2) != round(rec.nominal, 2): + raise UserError("Total estimasi harus sama dengan nominal PUM. Silakan sesuaikan rincian estimasi atau nominal PUM.") + @api.onchange('grand_total_reimburse', 'type_request') def _onchange_reimburse_line_update_nominal(self): if self.type_request == 'reimburse': @@ -751,7 +763,7 @@ class AdvancePaymentRequest(models.Model): pum_ids = self.search([ ('user_id', '=', user.id), - ('status', '!=', 'reject'), + ('status', '!=', 'cancel'), ('type_request', '=', 'pum') ]) @@ -911,6 +923,35 @@ class AdvancePaymentUsageLine(models.Model): compute='_compute_is_current_user_ap' ) + category_usage = fields.Selection([ + ('parkir', 'Parkir'), + ('tol', 'Tol'), + ('bbm', 'BBM'), + ('kuli', 'Kuli'), + ('konsumsi', 'Konsumsi'), + ('lain_lain', 'Lain-lain'), + ], string='Kategori System', compute='_compute_category_usage') + + @api.depends('account_id') + def _compute_category_usage(self): + for rec in self: + if not rec.account_id: + rec.category_usage = False + continue + name = rec.account_id.name.lower() + if 'bbm' in name or 'bahan bakar' in name: + rec.category_usage = 'bbm' + elif 'tol' in name: + rec.category_usage = 'tol' + elif 'parkir' in name: + rec.category_usage = 'parkir' + elif 'kuli' in name or 'bongkar' in name: + rec.category_usage = 'kuli' + elif 'konsumsi' in name or 'makan' in name or 'minum' in name: + rec.category_usage = 'konsumsi' + else: + rec.category_usage = 'lain_lain' + def _compute_is_current_user_ap(self): ap_user_ids = [23, 9468, 16729] is_ap = self.env.user.id in ap_user_ids @@ -1144,6 +1185,11 @@ class AdvancePaymentSettlement(models.Model): string="Is Current User AP", compute='_compute_is_current_user_ap' ) + pum_estimate_line_ids = fields.One2many( + related='pum_id.estimate_line_ids', + string='Rincian Estimasi PUM', + readonly=True + ) def _compute_is_current_user_ap(self): ap_user_ids = [23, 9468, 16729] @@ -1612,4 +1658,50 @@ class CreateReimburseCabWizard(models.TransientModel): 'type': 'ir.actions.act_window', 'res_id': move.id, 'target': 'current', - }
\ No newline at end of file + } + +class AdvancePaymentRequestEstimateLine(models.Model): + _name = 'advance.payment.request.estimate.line' + _description = 'Advance Payment Request Estimate Line' + + request_id = fields.Many2one('advance.payment.request', string='Request') + category_estimate = fields.Selection([ + ('parkir', 'Parkir'), + ('tol', 'Tol'), + ('bbm', 'BBM'), + ('kuli', 'Kuli'), + ('konsumsi', 'Konsumsi'), + ('lain_lain', 'Lain-lain'), + ], string='Kategori Estimasi', required=True) + description = fields.Text(string='Description', help='Deskripsi tambahan untuk estimasi biaya yang diperlukan.') + nominal = fields.Float(string='Nominal Estimasi', required=True, help='Masukkan nominal estimasi untuk kategori ini (tidak mesti akurat, hanya untuk gambaran umum).') + currency_id = fields.Many2one(related='request_id.currency_id') + + total_actual = fields.Float(string='Nominal Realisasi', compute='_compute_actual_data') + frequency = fields.Integer(string='Qty Realisasi', compute='_compute_actual_data') + + @api.depends('request_id.settlement_ids.penggunaan_line_ids.nominal', + 'request_id.settlement_ids.penggunaan_line_ids.account_id') + def _compute_actual_data(self): + for rec in self: + total_act = 0 + freq = 0 + if rec.request_id and rec.request_id.settlement_ids: + all_usage_lines = rec.request_id.settlement_ids.mapped('penggunaan_line_ids') + valid_lines = all_usage_lines.filtered(lambda l: l.account_id and l.category_usage) + planned_category = rec.request_id.estimate_line_ids.mapped('category_estimate') + if rec.category_estimate == 'lain_lain': + matched_lines = valid_lines.filtered( + lambda l: l.category_usage == 'lain_lain' or \ + l.category_usage not in planned_category + ) + else: + matched_lines = valid_lines.filtered( + lambda l: l.category_usage == rec.category_estimate + ) + + total_act = sum(matched_lines.mapped('nominal')) + freq = len(matched_lines) + rec.total_actual = total_act + rec.frequency = freq + diff --git a/indoteknik_custom/models/kartu_stock.py b/indoteknik_custom/models/kartu_stock.py new file mode 100644 index 00000000..22f90df0 --- /dev/null +++ b/indoteknik_custom/models/kartu_stock.py @@ -0,0 +1,186 @@ +from odoo import models +from io import BytesIO +import datetime +from base64 import encodebytes +import xlsxwriter + + +class KartuStokWizardInherit(models.TransientModel): + _inherit = 'kartu.stok.wizard' + + + def action_kartu_stok_excel_single_sheet(self): + + active_ids_tmp = self.env.context.get('active_ids') + active_model = self.env.context.get('active_model') + + if active_model == 'product.template': + active_ids = self.env['product.product'].search( + [('product_tmpl_id', 'in', active_ids_tmp), + ('active', '=', True)]).ids + else: + active_ids = active_ids_tmp + + data = { + 'location_id': self.location_id.id, + 'day_date': self.day_date, + 'previous_number_days': self.previous_number_days, + 'date_from': self.date_from, + 'date_to': self.date_to, + 'ids': active_ids, + 'context': {'active_model': active_model} + } + + file_io = BytesIO() + workbook = xlsxwriter.Workbook(file_io) + + self.generate_xlsx_single_sheet(workbook, data) + + workbook.close() + + fout = encodebytes(file_io.getvalue()) + datetime_string = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") + filename = f'Kartu_Stok_Single_{datetime_string}.xlsx' + + self.write({ + 'fileout': fout, + 'fileout_filename': filename + }) + + file_io.close() + + return { + 'type': 'ir.actions.act_url', + 'target': 'new', + 'url': 'web/content/?model=' + self._name + + '&id=' + str(self.id) + + '&field=fileout&download=true&filename=' + filename, + } + + def generate_xlsx_single_sheet(self, workbook, data): + + bold = workbook.add_format({'bold': True}) + border_date_right = workbook.add_format({'border':1, 'num_format': 'DD-MM', 'bg_color': '#dddddd', 'align': 'right'}) + border_int_right = workbook.add_format({'border':1, 'num_format': '#,##0', 'bg_color': '#dddddd', 'align': 'right'}) + border_int_right_bold = workbook.add_format({'border':1, 'num_format': '#,##0', 'bg_color': '#dddddd', 'align': 'right', 'bold': True}) + border_text_center = workbook.add_format({'border':1, 'bg_color': '#dddddd', 'align': 'center'}) + header_format = workbook.add_format({'bold': True, 'border':1, 'bg_color': '#808080', 'align': 'center'}) + + sheet = workbook.add_worksheet('Kartu Stok') + + docs = self.env['product.product'].browse(data['ids']) + location = self.env['stock.location'].browse(data['location_id']) + location_name = location.display_name.split('/')[0] + + row = 0 + + for doc in docs: + + # ========================= + # HEADER PRODUCT + # ========================= + sheet.write(row, 0, doc.display_name, bold) + row += 1 + sheet.write(row, 0, location_name, bold) + row += 2 + + # ========================= + # TABLE HEADER + # ========================= + sheet.write(row, 0, 'Date', header_format) + sheet.write(row, 1, 'In', header_format) + sheet.write(row, 2, 'Out', header_format) + sheet.write(row, 3, 'Stock', header_format) + sheet.write(row, 4, 'Distributor', header_format) + sheet.write(row, 5, 'Buyer', header_format) + sheet.write(row, 6, 'Document', header_format) + sheet.write(row, 7, 'Source Document', header_format) + row += 1 + + stock_total = 0 + stock_show_initial = False + + # ========================= + # MOVE LOOP (SAMA LOGIC ASLI) + # ========================= + for move in doc.stock_move_ids.sorted(key=lambda sm: sm.date): + for line in move.move_line_ids: + + if line.state != 'done': + continue + + if line.location_id.id != data['location_id'] and \ + line.location_dest_id.id != data['location_id']: + continue + + if not stock_show_initial: + sheet.write(row, 3, stock_total, border_int_right_bold) + sheet.write(row, 4, 'Initial Stock', border_text_center) + stock_show_initial = True + row += 1 + + qty_in = 0 + qty_out = 0 + + if line.location_dest_id.id == data['location_id']: + qty_in = line.qty_done + stock_total += qty_in + + if line.location_id.id == data['location_id']: + qty_out = line.qty_done + stock_total -= qty_out + + sheet.write(row, 0, line.date, border_date_right) + sheet.write(row, 1, qty_in, border_int_right) + sheet.write(row, 2, qty_out, border_int_right) + sheet.write(row, 3, stock_total, border_int_right) + # Distributor + col = 4 + if line.location_dest_id.id == data['location_id']: + if line.picking_id and line.picking_id.origin: + sheet.write(row, col, line.picking_id.partner_id.display_name, border_text_center) + else: + if line.location_id: + if line.location_id.name == 'Inventory adjustment': + sheet.write(row, col, 'Adjust *', border_text_center) + else: + sheet.write(row, col, line.location_id.location_id.name + ' *', border_text_center) + else: + sheet.write(row, col, doc.seller_ids[0].name + ' *' if doc.seller_ids else '', border_text_center) + else: + sheet.write(row, col, '', border_text_center) + # Buyer + col = 5 + if line.location_id.id == data['location_id']: + if line.picking_id and line.picking_id.origin: + sheet.write(row, col, line.picking_id.partner_id.display_name, border_text_center) + else: + if line.location_dest_id: + if line.location_dest_id.name == 'Inventory adjustment': + sheet.write(row, col, 'Adjust *', border_text_center) + else: + sheet.write(row, col, line.location_dest_id.location_id.name + ' *', border_text_center) + else: + sheet.write(row, col, doc.seller_ids[0].name + ' *' if doc.seller_ids else '', border_text_center) + else: + sheet.write(row, col, '', border_text_center) + # Document + col = 6 + if line.picking_id and line.picking_id.origin: + sheet.write(row, col, line.picking_id.name, border_text_center) + else: + sheet.write(row, col, line.reference or '', border_text_center) + # Source Document + col = 7 + if line.picking_id and line.picking_id.origin: + sheet.write(row, col, line.picking_id.origin, border_text_center) + else: + sheet.write(row, col, line.reference or '', border_text_center) + + row += 1 + + row += 3 # jarak antar product + + + + diff --git a/indoteknik_custom/models/keywords.py b/indoteknik_custom/models/keywords.py new file mode 100644 index 00000000..3fa9dd72 --- /dev/null +++ b/indoteknik_custom/models/keywords.py @@ -0,0 +1,253 @@ +from itertools import product +from multiprocessing import Condition +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__) +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' + _order= 'id desc' + + 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', + 'keywords_product_rel', + 'keyword_id', + 'product_id', + string='Products' + ) + name = fields.Char('Name', compute="_compute_name") + 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): + for record in self: + record.sum = len(record.product_ids) + + @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: + if not record.name: + record.name = record.keywords + + @api.constrains('keywords', 'category_id') + 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), + ('id', '!=', record.id) + ]) + if match: + raise UserError("Tidak bisa create karena keywords sudah dipakai") + + def copy(self): + raise UserError("Duplicate Record not allowed") + + + 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 or record.skip: + continue + + keyword = f"%{record.keywords.strip()}%" + + # AND (pt.unpublished IS FALSE OR pt.unpublished IS NULL) + sql = """ + SELECT DISTINCT pp.id + FROM product_product pp + JOIN product_template pt ON pt.id = pp.product_tmpl_id + JOIN product_public_category_product_template_rel rel + ON rel.product_template_id = pt.id + WHERE + pt.product_rating >= 8 + AND pp.active IS TRUE + AND ( + pt.name ILIKE %s + OR pt.website_description ILIKE %s + ) + """ + + params = [ + 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) + + 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}'" + ) + + 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 + ) + + @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 len(record.product_ids) > 0: + 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): + success_keywords = self.browse() + + for keyword in self: + if not keyword.product_ids: + _logger.info( + 'Skipping Solr sync for keyword "%s" - no products found', + keyword.keywords + ) + continue + + 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], + } + + solr.add([doc]) + + success_keywords |= keyword + + except Exception as e: + _logger.error( + "Solr sync failed for keyword ID %s: %s", + keyword.id, e + ) + + if success_keywords: + success_keywords.write({'solr_flag': 0}) + + 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") + return True + + keywords = self.browse(active_ids) + + documents = [] + for keyword in keywords: + # Skip syncing if product count is 0 + if len(keyword.product_ids) == 0: + _logger.info('Skipping Solr sync for keyword "%s" - no products found', keyword.keywords) + continue + + 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 + + @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) + + tracked_fields = ['keywords', 'category_id', 'product_ids', 'skip', 'name'] + neded_sync = any(field in vals for field in tracked_fields) + if neded_sync: + for record in self: + # Only flag for sync if there are products + if len(record.product_ids) > 0: + record.solr_flag = 2 + + return result diff --git a/indoteknik_custom/models/price_group.py b/indoteknik_custom/models/price_group.py index fce78fff..1a4f1bd6 100644 --- a/indoteknik_custom/models/price_group.py +++ b/indoteknik_custom/models/price_group.py @@ -10,16 +10,16 @@ class PriceGroup(models.Model): name = fields.Char(string='Name') pricelist_id = fields.Many2one('product.pricelist', string='Price List') - group1 = fields.Float(string='Kelompok 1 (%)') - group2 = fields.Float(string='Kelompok 2 (%)') - group3 = fields.Float(string='Kelompok 3 (%)') - group4 = fields.Float(string='Kelompok 4 (%)') - group5 = fields.Float(string='Kelompok 5 (%)') - group6 = fields.Float(string='Kelompok 6 (%)') - group7 = fields.Float(string='Kelompok 7 (%)') - group8 = fields.Float(string='Kelompok 8 (%)') - group9 = fields.Float(string='Kelompok 9 (%)') - group10 = fields.Float(string='Kelompok 10 (%)') + group1 = fields.Float(string='Kelompok 1 (%)', digits=(16, 12)) + group2 = fields.Float(string='Kelompok 2 (%)', digits=(16, 12)) + group3 = fields.Float(string='Kelompok 3 (%)', digits=(16, 12)) + group4 = fields.Float(string='Kelompok 4 (%)', digits=(16, 12)) + group5 = fields.Float(string='Kelompok 5 (%)', digits=(16, 12)) + group6 = fields.Float(string='Kelompok 6 (%)', digits=(16, 12)) + group7 = fields.Float(string='Kelompok 7 (%)', digits=(16, 12)) + group8 = fields.Float(string='Kelompok 8 (%)', digits=(16, 12)) + group9 = fields.Float(string='Kelompok 9 (%)', digits=(16, 12)) + group10 = fields.Float(string='Kelompok 10 (%)', digits=(16, 12)) def collect_price_group(self): PRICE_GROUP_ID = { diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py index 2c798f43..e10b4de2 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 @@ -78,7 +79,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') @@ -94,7 +95,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])]) @@ -121,7 +122,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: @@ -134,7 +135,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) @@ -147,25 +148,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 '' @@ -174,7 +175,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 @@ -213,7 +214,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) @@ -244,7 +245,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) @@ -336,7 +337,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') @@ -347,7 +348,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 = { @@ -361,7 +362,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: @@ -380,12 +381,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)) @@ -401,18 +402,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 = { @@ -428,10 +429,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 { @@ -442,7 +443,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 = {} @@ -455,7 +456,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 = {} @@ -474,13 +475,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 = {} @@ -488,7 +489,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 = {} @@ -497,18 +498,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 = { @@ -526,18 +527,18 @@ class ProductTemplate(models.Model): elif old_val != new_val: return f"<li><b>{label}</b>: image updated</li>" 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"<b>Product Attribute</b>:<br/>{attribute_name} added<br/>" if values: message += f"Values: '{values}'" @@ -546,7 +547,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: @@ -561,10 +562,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', '-') @@ -576,7 +577,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']: @@ -588,14 +589,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}'") @@ -605,7 +606,7 @@ class ProductTemplate(models.Model): message += "<br/>".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: @@ -619,7 +620,7 @@ class ProductTemplate(models.Model): else: attribute_name = 'Attribute' values = f"ID {cmd[1]}" - + message = f"<b>Product Attribute</b>:<br/>{attribute_name} removed<br/>" if values: message += f"Values: '{values}'" @@ -627,25 +628,25 @@ class ProductTemplate(models.Model): elif cmd[0] == 5: # Clear all self.message_post(body=f"<b>Product Attribute</b>:<br/>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"<li>Price: {vals['price']}</li>") if 'min_qty' in vals and vals['min_qty'] is not None: @@ -662,18 +663,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"<li>Unit of Measure: {uom.name}</li>") - + if details: detail_str = f" with:<ul>{''.join(details)}</ul>" else: detail_str = "" - + self.message_post(body=f"<b>Vendor Pricelist</b>: 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: @@ -694,10 +695,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': @@ -707,9 +708,9 @@ class ProductTemplate(models.Model): new_name = self.env['res.partner'].browse(new_value).name if new_value else 'None' changes.append(f"<li>Vendor: {old_name} → {new_name}</li>") continue - + old_value = old_data.get(field) - + # Format values based on field type if field == 'currency_id': if old_value != new_value: @@ -741,14 +742,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: @@ -757,20 +758,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"<li>{label}: {old_str} → {new_str}</li>") - + if changes: changes_str = f"<ul>{''.join(changes)}</ul>" self.message_post(body=f"<b>Vendor Pricelist</b>: 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"<li>Price: {vendor_data['price']}</li>") if vendor_data.get('min_qty'): @@ -779,7 +780,7 @@ class ProductTemplate(models.Model): details.append(f"<li>Product Name: {vendor_data['product_name']}</li>") if vendor_data.get('delay'): details.append(f"<li>Delivery Lead Time: {vendor_data['delay']}</li>") - + if details: detail_str = f"<ul>{''.join(details)}</ul>" else: @@ -795,7 +796,7 @@ class ProductTemplate(models.Model): details.append(f"<li>Quantity: {rec.min_qty}</li>") if rec.product_name: details.append(f"<li>Product Name: {rec.product_name}</li>") - + if details: detail_str = f"<ul>{''.join(details)}</ul>" else: @@ -803,12 +804,12 @@ class ProductTemplate(models.Model): else: name = f"ID {cmd[1]}" detail_str = "" - + self.message_post(body=f"<b>Vendor Pricelist</b>: removed '{name}'{detail_str}") - + elif cmd[0] == 5: # Clear all self.message_post(body=f"<b>Vendor Pricelist</b>: 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'] @@ -892,6 +893,7 @@ class ProductTemplate(models.Model): result = super().write(vals) # Log changes self._log_field_changes_product(vals, old_values) + return result # def write(self, vals): @@ -900,7 +902,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( @@ -938,7 +940,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') has_magento = fields.Boolean(string='Has Magento?', default=False, readonly=True) def generate_product_sla(self): @@ -962,7 +964,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') @@ -978,7 +980,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, @@ -1066,11 +1068,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([ @@ -1081,13 +1083,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 '' @@ -1096,7 +1098,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( @@ -1218,11 +1220,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([ @@ -1248,7 +1250,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', @@ -1325,7 +1327,8 @@ 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: if any(field in vals for field in tracked_fields): old_values = self._collect_old_values(vals) result = super().write(vals) @@ -1354,7 +1357,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/models/purchase_pricelist.py b/indoteknik_custom/models/purchase_pricelist.py index b3a473b6..83e06f55 100755 --- a/indoteknik_custom/models/purchase_pricelist.py +++ b/indoteknik_custom/models/purchase_pricelist.py @@ -118,15 +118,16 @@ class PurchasePricelist(models.Model): product_domain = [('product_id', '=', rec.product_id.id)] markup_pricelist = price_group['markup'].pricelist_id - base_price = price_incl + (price_incl * markup_percentage / 100) + # base_price = price_incl + (price_incl * markup_percentage / 100) + base_price = round(price_incl + (price_incl * markup_percentage / 100), 12) base_prod_pricelist = self.env['product.pricelist.item'].search(product_domain + [('pricelist_id', '=', markup_pricelist.id)], limit=1) base_prod_pricelist.fixed_price = base_price tier_percentages = [price_group[f'tier_{i}'][product_group] for i in range(1, 6)] for i, tier_percentage in enumerate(tier_percentages): tier_pricelist = price_group[f'tier_{i + 1}'].pricelist_id - tier_price = price_incl + (price_incl * tier_percentage / 100) - tier_perc = (base_price - tier_price) / base_price * 100 + tier_price = round(price_incl + (price_incl * tier_percentage / 100), 12) + tier_perc = round((base_price - tier_price) / base_price * 100, 12) tier_prod_pricelist = self.env['product.pricelist.item'].search(product_domain + [('pricelist_id', '=', tier_pricelist.id)], limit=1) tier_prod_pricelist.price_discount = tier_perc diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 2548c7b3..e6fc4732 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -647,7 +647,7 @@ class SaleOrder(models.Model): def _get_biteship_courier_codes(self): return [ - 'gojek','grab','deliveree','lalamove','jne','tiki','ninja','lion','rara','sicepat','jnt','pos','idexpress','rpx','wahana','jdl','pos','anteraja','sap','paxel','borzo' + 'gojek','grab','deliveree','lalamove','jne','ninja','lion','rara','sicepat','jnt','idexpress','rpx','wahana','jdl','anteraja','sap','paxel','borzo' ] @api.onchange('carrier_id') diff --git a/indoteknik_custom/models/shipment_group.py b/indoteknik_custom/models/shipment_group.py index ce4a9fdd..5f5f2d79 100644 --- a/indoteknik_custom/models/shipment_group.py +++ b/indoteknik_custom/models/shipment_group.py @@ -19,7 +19,7 @@ class ShipmentGroup(models.Model): total_colly_line = fields.Float(string='Total Colly', compute='_compute_total_colly_line') is_multi_partner = fields.Boolean(string='Is Multi Partner', compute='_compute_is_multi_partner') partner_ids = fields.Many2many('res.partner', string='Customers', compute='_compute_partner_ids') - driver = fields.Many2one('res.users', string='Driver') + driver = fields.Text(string='Driver') @api.depends('shipment_line.partner_id') def _compute_partner_ids(self): diff --git a/indoteknik_custom/models/stock_inventory.py b/indoteknik_custom/models/stock_inventory.py index b3580a4c..efd52e5c 100644 --- a/indoteknik_custom/models/stock_inventory.py +++ b/indoteknik_custom/models/stock_inventory.py @@ -21,7 +21,7 @@ class StockInventory(models.Model): ('logistic', 'Logistic'), ('accounting', 'Accounting'), ('approved', 'Approved'), - ], default='logistic', tracking=True, readonly=True) + ], tracking=True, readonly=True) def action_validate(self): if self.adjusment_type == 'out': @@ -38,7 +38,7 @@ class StockInventory(models.Model): if not self.env.user.has_group('indoteknik_custom.group_role_fat'): raise UserError("Adjustment Out harus dilakukan oleh Accounting") self.approval_state = 'approved' - return True + return super(StockInventory, self).action_validate() else: raise UserError("Adjustment Out harus melalui approval terlebih dahulu.") @@ -89,6 +89,12 @@ class StockInventory(models.Model): @api.model def create(self, vals): """Pastikan nomor hanya dibuat saat penyimpanan.""" + + if vals.get('adjusment_type') == 'in': + vals['approval_state'] = False + elif vals.get('adjusment_type') == 'out': + vals['approval_state'] = 'logistic' + if 'adjusment_type' in vals and not vals.get('number'): vals['number'] = False # Jangan buat number otomatis dulu @@ -105,6 +111,10 @@ class StockInventory(models.Model): res = super(StockInventory, self).write(vals) if 'adjusment_type' in vals: for record in self: + if record.adjusment_type == 'in': + record.approval_state = False + elif record.adjusment_type == 'out' and record.approval_state == False: + record.approval_state = 'logistic' self._assign_number(record) return res diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv index 50e2b382..2476afd6 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -200,6 +200,7 @@ access_advance_payment_usage_line,access.advance.payment.usage.line,model_advanc access_advance_payment_create_bill,access.advance.payment.create.bill,model_advance_payment_create_bill,,1,1,1,1 access_create_reimburse_cab_wizard_user,create.reimburse.cab.wizard user,model_create_reimburse_cab_wizard,,1,1,1,1 access_advance_payment_cancel_wizard,advance.payment.cancel.wizard,model_advance_payment_cancel_wizard,,1,1,1,1 +access_advance_payment_request_estimate_line,advance.payment.request.estimate.line,model_advance_payment_request_estimate_line,,1,1,1,1 access_purchasing_job_seen,purchasing.job.seen,model_purchasing_job_seen,,1,1,1,1 access_tukar_guling_all_users,tukar.guling.all.users,model_tukar_guling,base.group_user,1,1,1,1 @@ -218,3 +219,5 @@ access_stock_picking_sj_document,stock.picking.sj.document,model_stock_picking_s access_gudang_service,gudang.service,model_gudang_service,base.group_user,1,1,1,1 access_gudang_service_line,gudang.service.line,model_gudang_service_line,base.group_user,1,1,1,1 access_update_depreciation_move_wizard,access.update.depreciation.move.wizard,model_update_depreciation_move_wizard,,1,1,1,1 +access_keywords,keywords,model_keywords,base.group_user,1,1,1,1 + diff --git a/indoteknik_custom/views/account_move_line.xml b/indoteknik_custom/views/account_move_line.xml index 838596c8..346494f3 100644 --- a/indoteknik_custom/views/account_move_line.xml +++ b/indoteknik_custom/views/account_move_line.xml @@ -9,6 +9,9 @@ <!-- <xpath expr="//page[@id='aml_tab']/field[@name='line_ids']" position="attributes"> <attribute name="attrs">{'readonly': [('refund_id','!=',False)]}</attribute> </xpath> --> + <xpath expr="//field[@name='line_ids']/tree/field[@name='credit']" position="after"> + <field name="date_maturity" optional="hide"/> + </xpath> <xpath expr="//page[@id='aml_tab']/field[@name='line_ids']/tree/field[@name='currency_id']" position="before"> <field name="is_required" invisible="1"/> </xpath> diff --git a/indoteknik_custom/views/advance_payment_request.xml b/indoteknik_custom/views/advance_payment_request.xml index 340e0caf..4faf905e 100644 --- a/indoteknik_custom/views/advance_payment_request.xml +++ b/indoteknik_custom/views/advance_payment_request.xml @@ -134,8 +134,28 @@ <br/> </group> </group> - <notebook attrs="{'invisible': [('type_request', '!=', 'reimburse')]}"> - <page string="Rincian Reimburse"> + <notebook> + <page string="Rincian Estimasi PUM" attrs="{'invisible': [('type_request', '!=', 'pum')]}"> + <p style="font-size: 12px; color: grey; font-style: italic">*Masukkan estimasi alokasi biaya sebagai gambaran rencana penggunaan dana, tidak harus diisi dengan nominal yang akurat</p> + <field name="estimate_line_ids"> + <tree> + <field name="category_estimate"/> + <field name="description"/> + <field name="nominal" sum="Total"/> + <field name="currency_id" invisible="1"/> + </tree> + <form> + <group col="2"> + <field name="request_id" invisible="1"/> + <field name="category_estimate"/> + <field name="description" placeholder="Deskripsi tambahan untuk rincian estimasi..."/> + <field name="nominal"/> + <field name="currency_id" invisible="1"/> + </group> + </form> + </field> + </page> + <page string="Rincian Reimburse" attrs="{'invisible': [('type_request', '!=', 'reimburse')]}"> <field name="reimburse_line_ids"> <tree> <field name="sequence" widget="handle"/> diff --git a/indoteknik_custom/views/advance_payment_settlement.xml b/indoteknik_custom/views/advance_payment_settlement.xml index a8bf1de7..352c5b96 100644 --- a/indoteknik_custom/views/advance_payment_settlement.xml +++ b/indoteknik_custom/views/advance_payment_settlement.xml @@ -118,12 +118,26 @@ <group string="Finance"> <field name="is_current_user_ap" invisible="1"/> <field name="account_id" attrs="{'readonly': [('is_current_user_ap', '=', False)]}"/> + <field name="category_usage" invisible="1"/> <field name="done_attachment" attrs="{'readonly': [('is_current_user_ap', '=', False)]}"/> </group> </group> </form> </field> </page> + <page string="Rincian Estimasi PUM"> + <p style="font-size: 12px; color: grey; font-style: italic">*Rincian estimasi PUM ini hanya sebagai gambaran umum untuk realisasi yang dilakukan, tidak harus diisi dengan nominal yang akurat.</p> + <field name="pum_estimate_line_ids" nolabel="1"> + <tree> + <field name="category_estimate"/> + <field name="description"/> + <field name="nominal" sum="Total Estimasi"/> + <field name="frequency"/> + <field name="total_actual" sum="Total Actual"/> + <field name="currency_id" invisible="1"/> + </tree> + </field> + </page> </notebook> <div style="text-align:right;"> diff --git a/indoteknik_custom/views/find_page.xml b/indoteknik_custom/views/find_page.xml index c752aa98..fc9bddbb 100644 --- a/indoteknik_custom/views/find_page.xml +++ b/indoteknik_custom/views/find_page.xml @@ -25,7 +25,7 @@ <group> <field name="category_id"/> <field name="brand_id"/> - <field name="url"/> + <field name="url" /> </group> <group> <field name="create_uid"/> @@ -62,9 +62,18 @@ <field name="view_mode">tree,form</field> </record> + + <record id="ir_actions_server_find_page_sync_to_solr" model="ir.actions.server"> + <field name="name">Sync to solr</field> + <field name="model_id" ref="indoteknik_custom.model_web_find_page"/> + <field name="binding_model_id" ref="indoteknik_custom.model_web_find_page"/> + <field name="state">code</field> + <field name="code">model._sync_to_solr()</field> + </record> + <menuitem id="menu_web_find_page" name="Web Find Page" action="web_find_page_action" parent="website_sale.menu_orders" sequence="8"/> -</odoo>
\ No newline at end of file +</odoo> diff --git a/indoteknik_custom/views/kartu_stock.xml b/indoteknik_custom/views/kartu_stock.xml new file mode 100644 index 00000000..705d86a2 --- /dev/null +++ b/indoteknik_custom/views/kartu_stock.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<odoo> + <data> + <record id="kartu_stok_wizard_form_inherit_single_excel" model="ir.ui.view"> + <field name="name">kartu.stok.wizard.form.inherit.single.excel</field> + <field name="model">kartu.stok.wizard</field> + <field name="inherit_id" ref="proweb_kartu_stok.print_kartu_stok_view_form"/> + <field name="arch" type="xml"> + <xpath expr="//footer" position="inside"> + + <button name="action_kartu_stok_excel_single_sheet" type="object" string="Print Excel (Single Sheet)" class="btn-primary"/> + + </xpath> + </field> + </record> + </data> +</odoo>
\ No newline at end of file diff --git a/indoteknik_custom/views/keywords.xml b/indoteknik_custom/views/keywords.xml new file mode 100644 index 00000000..a33edae0 --- /dev/null +++ b/indoteknik_custom/views/keywords.xml @@ -0,0 +1,96 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<odoo> + <record id="keywords_tree" model="ir.ui.view"> + <field name="name">keywords.tree</field> + <field name="model">keywords</field> + <field name="arch" type="xml"> + <tree > + <field name="category_id" /> + <field name="keywords" /> + <field name="url" /> + <field name="sum" /> + <field name="solr_flag" readonly="1"/> + <field name="product_ids" widget="many2many_tags" /> + </tree> + </field> + </record> + + <record id="keywords_form" model="ir.ui.view"> + <field name="name">keywords.form</field> + <field name="model">keywords</field> + <field name="arch" type="xml"> + <form> + <header> + <button name="generate_products" string="Generate Product Manual" type="object" class="oe_highlight"/> + <button name="clear_products" string="Clear Generated Products" type="object" /> + </header> + <sheet> + <div class="oe_title"> + <h1> + <field name="name" readonly="1" class="oe_inline"/> + </h1> + </div> + <group> + <field name="category_id" /> + <field name="keywords" /> + <field name="url" /> + <field name="sum" /> + <field name="product_ids" widget="many2many_tags" /> + <field name="skip" /> + <field name="solr_flag" readonly="1"/> + </group> + </sheet> + </form> + </field> + </record> + + <record id="view_keywords_filter" model="ir.ui.view"> + <field name="name">keywords.list.select</field> + <field name="model">keywords</field> + <field name="priority" eval="15"/> + <field name="arch" type="xml"> + <search string="Search Keywords"> + <field name="category_id"/> + <field name="keywords"/> + <field name="sum"/> + <field name="solr_flag" readonly="1"/> + <field name="product_ids" widget="many2many_tags"/> + </search> + </field> + </record> + <record id="action_keywords" model="ir.actions.act_window"> + <field name="name">Keywords</field> + <field name="type">ir.actions.act_window</field> + <field name="res_model">keywords</field> + <field name="search_view_id" ref="view_keywords_filter"/> + <field name="view_mode">tree,form</field> + </record> + + <record id="ir_actions_server_keywords_sync_to_solr" model="ir.actions.server"> + <field name="name">Sync to solr</field> + <field name="model_id" ref="indoteknik_custom.model_keywords"/> + <field name="binding_model_id" ref="indoteknik_custom.model_keywords"/> + <field name="state">code</field> + <field name="code">model.sync_solr()</field> + </record> + <menuitem id="menu_keywords" + name="Keywords" + parent="website_sale.menu_orders" + action="action_keywords" + sequence="100"/> + + <data noupdate="1"> + <record id="cron_keywords_solr_flag_queue" model="ir.cron"> + <field name="name">Sync Keywords To Solr: Queue Solr Flag 2</field> + <field name="interval_number">1</field> + <field name="interval_type">hours</field> + <field name="numbercall">-1</field> + <field name="doall" eval="False"/> + <field name="model_id" ref="model_keywords"/> + <field name="code">model.solr_flag_to_queue(limit=500)</field> + <field name="state">code</field> + <field name="priority">55</field> + <field name="active">False</field> + </record> + </data> +</odoo> |
