summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xindoteknik_custom/__manifest__.py11
-rwxr-xr-xindoteknik_custom/models/__init__.py3
-rw-r--r--indoteknik_custom/models/advance_payment_request.py96
-rw-r--r--indoteknik_custom/models/kartu_stock.py186
-rw-r--r--indoteknik_custom/models/keywords.py253
-rw-r--r--indoteknik_custom/models/price_group.py20
-rwxr-xr-xindoteknik_custom/models/product_template.py175
-rwxr-xr-xindoteknik_custom/models/purchase_pricelist.py7
-rwxr-xr-xindoteknik_custom/models/sale_order.py2
-rw-r--r--indoteknik_custom/models/shipment_group.py2
-rw-r--r--indoteknik_custom/models/stock_inventory.py14
-rwxr-xr-xindoteknik_custom/security/ir.model.access.csv3
-rw-r--r--indoteknik_custom/views/account_move_line.xml3
-rw-r--r--indoteknik_custom/views/advance_payment_request.xml24
-rw-r--r--indoteknik_custom/views/advance_payment_settlement.xml14
-rw-r--r--indoteknik_custom/views/find_page.xml13
-rw-r--r--indoteknik_custom/views/kartu_stock.xml17
-rw-r--r--indoteknik_custom/views/keywords.xml96
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>