summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorit-fixcomart <it@fixcomart.co.id>2025-03-13 10:11:33 +0700
committerit-fixcomart <it@fixcomart.co.id>2025-03-13 10:11:33 +0700
commited9a8ebd0e3a16ac501da848fd4dbc5ae0ce6ff5 (patch)
tree5585297ea85e86eaa09da2d0c20373fd64bd8c5b
parent60de643d4a5f19abc7bee34ccd2e6e6f6219a750 (diff)
parent1ff9a57e2f7a7ecb3ba9321f7133f43e7009aa47 (diff)
Merge branch 'odoo-backup' into CR/renca-banner
-rw-r--r--indoteknik_api/controllers/api_v1/partner.py590
-rw-r--r--indoteknik_api/controllers/api_v1/product.py81
-rw-r--r--indoteknik_api/controllers/api_v1/sale_order.py4
-rw-r--r--indoteknik_api/controllers/api_v1/stock_picking.py50
-rw-r--r--indoteknik_api/models/res_partner.py8
-rwxr-xr-xindoteknik_custom/__manifest__.py2
-rwxr-xr-xindoteknik_custom/models/__init__.py2
-rw-r--r--indoteknik_custom/models/account_move.py3
-rw-r--r--indoteknik_custom/models/commision.py15
-rw-r--r--indoteknik_custom/models/product_sla.py133
-rwxr-xr-xindoteknik_custom/models/product_template.py12
-rw-r--r--indoteknik_custom/models/public_holiday.py11
-rw-r--r--indoteknik_custom/models/res_partner.py26
-rwxr-xr-xindoteknik_custom/models/sale_order.py212
-rw-r--r--indoteknik_custom/models/stock_inventory.py26
-rw-r--r--indoteknik_custom/models/stock_picking.py213
-rw-r--r--indoteknik_custom/models/user_pengajuan_tempo.py3
-rw-r--r--indoteknik_custom/models/user_pengajuan_tempo_request.py29
-rw-r--r--indoteknik_custom/models/vendor_sla.py102
-rwxr-xr-xindoteknik_custom/security/ir.model.access.csv4
-rw-r--r--indoteknik_custom/views/customer_commision.xml4
-rw-r--r--indoteknik_custom/views/product_product.xml8
-rw-r--r--indoteknik_custom/views/product_sla.xml7
-rw-r--r--indoteknik_custom/views/public_holiday.xml55
-rw-r--r--indoteknik_custom/views/res_partner.xml3
-rwxr-xr-xindoteknik_custom/views/sale_order.xml4
-rw-r--r--indoteknik_custom/views/stock_picking.xml6
-rw-r--r--indoteknik_custom/views/user_pengajuan_tempo.xml2
-rw-r--r--indoteknik_custom/views/user_pengajuan_tempo_request.xml3
-rw-r--r--indoteknik_custom/views/vendor_sla.xml42
-rwxr-xr-xindoteknik_custom/views/x_banner_category.xml2
31 files changed, 1260 insertions, 402 deletions
diff --git a/indoteknik_api/controllers/api_v1/partner.py b/indoteknik_api/controllers/api_v1/partner.py
index 307165b3..126fded4 100644
--- a/indoteknik_api/controllers/api_v1/partner.py
+++ b/indoteknik_api/controllers/api_v1/partner.py
@@ -64,37 +64,43 @@ class Partner(controller.Controller):
@http.route(prefix + 'partner/<id>/address', auth='public', methods=['PUT', 'OPTIONS'], csrf=False)
@controller.Controller.must_authorized()
def write_partner_address_by_id(self, **kw):
- params = self.get_request_params(kw, {
- 'id': ['required', 'number'],
- 'type': ['default:other'],
- 'name': ['required'],
- 'email': ['required'],
- 'mobile': ['required'],
- 'phone': [''],
- 'street': ['required'],
- 'state_id': ['required', 'number', 'alias:state_id'],
- 'city_id': ['required', 'number', 'alias:kota_id'],
- 'district_id': ['number', 'alias:kecamatan_id'],
- 'sub_district_id': ['number', 'alias:kelurahan_id', 'exclude_if_null'],
- 'zip': ['required'],
- 'longtitude': [],
- 'latitude': [],
- 'address_map': [],
- 'alamat_lengkap_text': []
- })
+ try:
+ params = self.get_request_params(kw, {
+ 'id': ['required', 'number'],
+ 'type': ['default:other'],
+ 'name': ['required'],
+ 'email': ['required'],
+ 'mobile': ['required'],
+ 'phone': [''],
+ 'street': ['required'],
+ 'state_id': ['required', 'number', 'alias:state_id'],
+ 'city_id': ['required', 'number', 'alias:kota_id'],
+ 'district_id': ['number', 'alias:kecamatan_id'],
+ 'sub_district_id': ['number', 'alias:kelurahan_id', 'exclude_if_null'],
+ 'zip': ['required'],
+ 'longtitude': '',
+ 'latitude': '',
+ 'address_map': [],
+ 'alamat_lengkap_text': []
+ })
- if not params['valid']:
- return self.response(code=400, description=params)
-
- partner = request.env[self._name].search([('id', '=', params['value']['id'])], limit=1)
- if not partner:
- return self.response(code=404, description='User not found')
-
- partner.write(params['value'])
+ if not params['valid']:
+ return self.response(code=400, description=params)
- return self.response({
- 'id': partner.id
- })
+ partner = request.env[self._name].sudo().search([('id', '=', params['value']['id'])], limit=1)
+
+ if not partner:
+ return self.response(code=404, description='User not found')
+
+ try:
+ partner.write(params['value'])
+ except Exception as e:
+ return self.response(code=500, description=f'Error writing partner data: {str(e)}')
+
+ return self.response({'id': partner.id})
+
+ except Exception as e:
+ return self.response(code=500, description=f'Unexpected error: {str(e)}')
@http.route(prefix + 'partner/address', auth='public', methods=['POST', 'OPTIONS'], csrf=False)
@controller.Controller.must_authorized()
@@ -111,8 +117,8 @@ class Partner(controller.Controller):
'city_id': ['required', 'number', 'alias:kota_id'],
'district_id': ['number', 'alias:kecamatan_id'],
'sub_district_id': ['number', 'alias:kelurahan_id', 'exclude_if_null'],
- 'longtitude': [],
- 'latitude': [],
+ 'longtitude': '',
+ 'latitude': '',
'address_map': [],
'zip': ['required']
})
@@ -303,270 +309,310 @@ class Partner(controller.Controller):
@http.route(prefix + 'partner/pengajuan_tempo', auth='public', methods=['POST'], csrf=False)
@controller.Controller.must_authorized()
def write_pengajuan_tempo(self, **kw):
- id = int(kw.get('partner_id'))
- user_id = int(kw.get('user_id'))
- tempo_request = True if kw.get('tempo_request') == 'true' else False
- pengajuan_tempo = request.env['user.pengajuan.tempo'].search([('name_tempo', '=', user_id)], limit=1)
- user = request.env['res.partner'].search([('id', '=', user_id)], limit=1)
- company_name = kw.get('name', pengajuan_tempo.name_tempo.name)
- partner_id = request.env['res.partner'].search([('name', 'like', company_name)], limit=1)
- user_account = self.get_user_by_email(user.email)
-
- params = self.get_request_params(kw, {
-
- # informasi perusahaan
- # 'name': ['required', 'alias:name_tempo'],
- 'industryId': ['alias:industry_id_tempo'],
- 'street': ['alias:street_tempo'],
- 'state': ['alias:state_id_tempo'],
- 'city': ['alias:city_id_tempo'],
- 'district': ['alias:district_id_tempo'],
- 'subDistrict': ['alias:subDistrict_id_tempo'],
- 'zip': ['alias:zip_tempo'],
- 'mobile': ['alias:mobile_tempo'],
- 'bankName': ['alias:bank_name_tempo'],
- 'accountName': ['alias:account_name_tempo'],
- 'accountNumber': ['alias:account_number_tempo'],
- 'website': ['alias:website_tempo'],
- 'estimasi': ['alias:estimasi_tempo'],
- 'portal': ['alias:portal'],
- 'bersedia': ['alias:bersedia'],
- 'tempoDuration': ['alias:tempo_duration'],
- 'tempoLimit': ['alias:tempo_limit'],
-
- # informasi perusahaan
- 'direkturTittle': ['alias:direktur_tittle'],
- 'direkturName': ['alias:direktur_name'],
- 'direkturMobile': ['alias:direktur_mobile'],
- 'direkturEmail': ['alias:direktur_email'],
- 'purchasingTittle': ['alias:purchasing_tittle'],
- 'purchasingName': ['alias:purchasing_name'],
- 'purchasingMobile': ['alias:purchasing_mobile'],
- 'purchasingEmail': ['alias:purchasing_email'],
- 'financeTittle': ['alias:finance_tittle'],
- 'financeName': ['alias:finance_name'],
- 'financeMobile': ['alias:finance_mobile'],
- 'financeEmail': ['alias:finance_email'],
-
- # Pengiriman
- 'PICTittle': ['alias:pic_tittle'],
- 'PICName': ['alias:pic_name'],
- 'streetPengiriman': ['alias:street_pengiriman'],
- 'statePengiriman': ['alias:state_id_pengiriman'],
- 'cityPengiriman': ['alias:city_id_pengiriman'],
- 'districtPengiriman': ['alias:district_id_pengiriman'],
- 'subDistrictPengiriman': ['alias:subDistrict_id_pengiriman'],
- 'zipPengiriman': ['alias:zip_pengiriman'],
- 'invoicePicTittle': ['alias:invoice_pic_tittle'],
- 'invoicePic': ['alias:invoice_pic'],
- 'streetInvoice': ['alias:street_invoice'],
- 'stateInvoice': ['alias:state_id_invoice'],
- 'cityInvoice': ['alias:city_id_invoice'],
- 'districtInvoice': ['alias:district_id_invoice'],
- 'subDistrictInvoice': ['alias:subDistrict_id_invoice'],
- 'zipInvoice': ['alias:zip_invoice'],
- 'isSameAddrees':['alias:is_same_address'],
- 'isSameAddreesStreet':['alias:is_same_address_street'],
- })
-
- # # Konversi nilai 'true' ke boolean True
- # is_same_address = kw.get('isSameAddrees', 'false').lower() == 'true'
- # is_same_address_street = kw.get('isSameAddreesStreet', 'false').lower() == 'true'
- #
- # # Tambahkan nilai yang dikonversi ke params
- # if 'isSameAddress' in kw:
- # params['value']['is_same_address'] = is_same_address
- # if 'is_same_address_street' in kw:
- # params['value']['is_same_address_street'] = is_same_address_street
+ try:
+ id = int(kw.get('partner_id'))
+ user_id = int(kw.get('user_id'))
+ tempo_request = True if kw.get('tempo_request') == 'true' else False
+ pengajuan_tempo = request.env['user.pengajuan.tempo'].search([('name_tempo', '=', user_id)], limit=1)
+ user = request.env['res.partner'].search([('id', '=', user_id)], limit=1)
+ company_name = kw.get('name', pengajuan_tempo.name_tempo.name)
+ partner_id = request.env['res.partner'].search([('name', 'like', company_name)], limit=1)
+ user_account = self.get_user_by_email(user.email)
+ dokumen_prosedur = False
+ if kw.get('formDokumenProsedur') and kw.get('formDokumenProsedur') != 'false':
+ dokumen_prosedur = kw.get('formDokumenProsedur')
+ params = self.get_request_params(kw, {
+
+ # informasi perusahaan
+ # 'name': ['required', 'alias:name_tempo'],
+ 'industryId': ['alias:industry_id_tempo'],
+ 'street': ['alias:street_tempo'],
+ 'state': ['alias:state_id_tempo'],
+ 'city': ['alias:city_id_tempo'],
+ 'district': ['alias:district_id_tempo'],
+ 'subDistrict': ['alias:subDistrict_id_tempo'],
+ 'zip': ['alias:zip_tempo'],
+ 'mobile': ['alias:mobile_tempo'],
+ 'bankName': ['alias:bank_name_tempo'],
+ 'accountName': ['alias:account_name_tempo'],
+ 'accountNumber': ['alias:account_number_tempo'],
+ 'website': ['alias:website_tempo'],
+ 'estimasi': ['alias:estimasi_tempo'],
+ 'portal': ['alias:portal'],
+ 'bersedia': ['alias:bersedia'],
+ 'tempoDuration': ['alias:tempo_duration'],
+ 'tempoLimit': ['alias:tempo_limit'],
+
+ # informasi perusahaan
+ 'direkturTittle': ['alias:direktur_tittle'],
+ 'direkturName': ['alias:direktur_name'],
+ 'direkturMobile': ['alias:direktur_mobile'],
+ 'direkturEmail': ['alias:direktur_email'],
+ 'purchasingTittle': ['alias:purchasing_tittle'],
+ 'purchasingName': ['alias:purchasing_name'],
+ 'purchasingMobile': ['alias:purchasing_mobile'],
+ 'purchasingEmail': ['alias:purchasing_email'],
+ 'financeTittle': ['alias:finance_tittle'],
+ 'financeName': ['alias:finance_name'],
+ 'financeMobile': ['alias:finance_mobile'],
+ 'financeEmail': ['alias:finance_email'],
+
+ # Pengiriman
+ 'PICTittle': ['alias:pic_tittle'],
+ 'PICBarangMobile': ['alias:pic_mobile'],
+ 'PICName': ['alias:pic_name'],
+ 'streetPengiriman': ['alias:street_pengiriman'],
+ 'statePengiriman': ['alias:state_id_pengiriman'],
+ 'cityPengiriman': ['alias:city_id_pengiriman'],
+ 'districtPengiriman': ['alias:district_id_pengiriman'],
+ 'subDistrictPengiriman': ['alias:subDistrict_id_pengiriman'],
+ 'zipPengiriman': ['alias:zip_pengiriman'],
+ 'invoicePicTittle': ['alias:invoice_pic_tittle'],
+ 'invoicePicMobile': ['alias:invoice_pic_mobile'],
+ 'invoicePic': ['alias:invoice_pic'],
+ 'streetInvoice': ['alias:street_invoice'],
+ 'stateInvoice': ['alias:state_id_invoice'],
+ 'cityInvoice': ['alias:city_id_invoice'],
+ 'districtInvoice': ['alias:district_id_invoice'],
+ 'subDistrictInvoice': ['alias:subDistrict_id_invoice'],
+ 'zipInvoice': ['alias:zip_invoice'],
+ 'isSameAddrees':['alias:is_same_address'],
+ 'isSameAddreesStreet':['alias:is_same_address_street'],
+ })
- if not params['valid']:
- return self.response(code=400, description=params)
- if params['value']['portal']:
- if params['value']['portal'] == 'ada':
- params['value']['portal'] = True
+ # # Konversi nilai 'true' ke boolean True
+ # is_same_address = kw.get('isSameAddrees', 'false').lower() == 'true'
+ # is_same_address_street = kw.get('isSameAddreesStreet', 'false').lower() == 'true'
+ #
+ # # Tambahkan nilai yang dikonversi ke params
+ # if 'isSameAddress' in kw:
+ # params['value']['is_same_address'] = is_same_address
+ # if 'is_same_address_street' in kw:
+ # params['value']['is_same_address_street'] = is_same_address_street
+
+ if not params['valid']:
+ return self.response(code=400, description=params)
+ if params['value']['portal']:
+ if params['value']['portal'] == 'ada':
+ params['value']['portal'] = True
+ else:
+ params['value']['portal'] = False
+ # Filter data baru yang dikirim (non-kosong, boolean False tetap masuk)
+ new_data = {key: value for key, value in params['value'].items() if value != ''}
+
+ if pengajuan_tempo:
+ try:
+ pengajuan_tempo.write(new_data)
+ except Exception as e:
+ return self.response(code=500, description=f'Error updating partner data: {str(e)}')
else:
- params['value']['portal'] = False
- # Filter data baru yang dikirim (non-kosong, boolean False tetap masuk)
- new_data = {key: value for key, value in params['value'].items() if value != ''}
+ try:
+ pengajuan_tempo = request.env['user.pengajuan.tempo'].create(new_data)
+ pengajuan_tempo.partner_id = user_id
+ except Exception as e:
+ return self.response(code=500, description=f'Error creating partner data: {str(e)}')
+
+ if partner_id:
+ try:
+ pengajuan_tempo.name_tempo = partner_id
+ except Exception as e:
+ return self.response(code=500, description=f'Error updating partner data: {str(e)}')
+
+ # Prosedur Pengiriman
+ if dokumen_prosedur:
+ dokumen_prosedur = json.loads(dokumen_prosedur)
+ mimetype, _ = mimetypes.guess_type(dokumen_prosedur['name'])
+ mimetype = mimetype or 'application/octet-stream'
+ data = base64.b64decode(dokumen_prosedur['base64'])
+ dokumen_prosedur_attachment = request.env['ir.attachment'].create({
+ 'name': dokumen_prosedur['name'],
+ 'type': 'binary',
+ 'datas': base64.b64encode(data),
+ 'res_model': 'user.pengajuan.tempo',
+ 'res_id': pengajuan_tempo.id,
+ 'mimetype': mimetype
+ })
+ pengajuan_tempo.dokumen_prosedur = [(6, 0, [dokumen_prosedur_attachment.id])]
+ pengajuan_tempo.message_post(body="Dokumen Prosedur", attachment_ids=[dokumen_prosedur_attachment.id])
+
+
+ form_supplier_data = kw.get('formSupplier', False)
+
+ if form_supplier_data:
+ try:
+ form_supplier_data = json.loads(form_supplier_data)
+
+ supplier_ids_to_add = []
+ for item in form_supplier_data:
+ supplier_name = item.get("supplier")
+ pic_name = item.get("pic")
+ phone = item.get("telepon")
+ tempo_duration = item.get("durasiTempo")
+ credit_limit = item.get("creditLimit")
+
+ new_data = {
+ 'name_supplier': supplier_name,
+ 'pic_name': pic_name,
+ 'phone': phone,
+ 'tempo_duration': tempo_duration,
+ 'credit_limit': credit_limit,
+ }
+ new_supplier_data = request.env['user.pengajuan.tempo.line'].create(new_data)
+
+ supplier_ids_to_add.append(new_supplier_data.id)
+
+ pengajuan_tempo.write({'supplier_ids': [(6, 0, supplier_ids_to_add)]})
+
+ except json.JSONDecodeError:
+ return http.Response(status=400, json_body={'error': 'Invalid JSON format for formSupplier'})
+ category_produk_ids = kw.get('categoryProduk', False)
+ category_ids = ''
+ if category_produk_ids:
+ try:
+ category_ids = list(map(int, category_produk_ids.split(',')))
+ pengajuan_tempo.category_produk_ids = [(6, 0, category_ids)]
+ except Exception as e:
+ return self.response(code=500, description=f'Unexpected error: {str(e)}')
+
+
+ tukar_invoice_input = kw.get('tukarInvoiceInput')
+ if tukar_invoice_input:
+ pengajuan_tempo.tukar_invoice = tukar_invoice_input
+
+ tukar_invoice_input_pembayaran = kw.get('tukarInvoiceInputPembayaran')
+ if tukar_invoice_input_pembayaran:
+ pengajuan_tempo.jadwal_bayar = tukar_invoice_input_pembayaran
+
+ dokumen_kirim = [
+ 'Surat Tanda Terima Barang (STTB)',
+ 'Good Receipt (GR)',
+ 'Surat Terima Barang (STB)',
+ 'Lembar Penerimaan Barang (LPB)'
+ ]
+
+ dokumen_kirim_barang_ids = kw.get('dokumenPengiriman')
+ dokumen_kirim_input = kw.get('dokumenKirimInput', '')
+ dokumen_kirim_barang_input = kw.get('dokumenPengirimanInput', '')
+ dokumen_kirim_barang = []
+
+ if dokumen_kirim_barang_ids:
+ dokumen_kirim_ids = list(map(int, dokumen_kirim_barang_ids.split(',')))
+ dokumen_kirim_barang = [dokumen_kirim[i] for i in dokumen_kirim_ids if 0 <= i < len(dokumen_kirim)]
+ if dokumen_kirim_input:
+ input_items = [item.strip() for item in dokumen_kirim_input.split(',')]
+ dokumen_kirim_barang.extend(item for item in input_items if item and item not in dokumen_kirim_barang)
+ pengajuan_tempo.dokumen_kirim_input = dokumen_kirim_input
+ if dokumen_kirim_barang:
+ pengajuan_tempo.dokumen_pengiriman = ', '.join(dokumen_kirim_barang)
+ if dokumen_kirim_barang_input:
+ pengajuan_tempo.dokumen_pengiriman_input = dokumen_kirim_barang_input
+
+ dokumen = [
+ 'Invoice Pembelian',
+ 'Surat Jalan',
+ 'Berita Acara Serah Terima (BAST)',
+ 'Faktur Pajak',
+ 'Good Receipt (GR)'
+ ]
+
+ dokumen_invoice = kw.get('dokumenPengirimanInvoice', '')
+ if dokumen_invoice:
+ pengajuan_tempo.dokumen_invoice = dokumen_invoice
+ user_tempo_request = []
+ if tempo_request:
+ user_tempo_request = request.env['user.pengajuan.tempo.request'].create({
+ 'user_id': id,
+ 'pengajuan_tempo_id': pengajuan_tempo.id,
+ 'user_company_id': partner_id.id,
+ 'tempo_duration': pengajuan_tempo.tempo_duration.id,
+ 'tempo_limit': pengajuan_tempo.tempo_limit,
+ })
- if pengajuan_tempo:
- # Jika pengajuan_tempo sudah ada, hanya write data baru yang non-kosong
- pengajuan_tempo.write(new_data)
- else:
- # Jika belum ada, buat record baru
- pengajuan_tempo = request.env['user.pengajuan.tempo'].create(new_data)
- pengajuan_tempo.partner_id = user_id
+ form_dokumen_data = kw.get('formDocs', False)
+ if form_dokumen_data:
+ try:
+ form_dokumen = json.loads(form_dokumen_data)
- if partner_id:
- pengajuan_tempo.name_tempo = partner_id
+ for dokumen in form_dokumen:
+ if dokumen['details']['base64'] != '':
+ mimetype, _ = mimetypes.guess_type(dokumen['details']['name'])
+ mimetype = mimetype or 'application/octet-stream'
+ data = base64.b64decode(dokumen['details']['base64'])
+ sppkp_attachment = request.env['ir.attachment'].create({
+ 'name': dokumen['details']['name'],
+ 'type': 'binary',
+ 'datas': base64.b64encode(data),
+ 'res_model': 'user.pengajuan.tempo',
+ 'res_id': pengajuan_tempo.id,
+ 'mimetype': mimetype
+ })
- form_supplier_data = kw.get('formSupplier', False)
+ if dokumen['documentName'] == 'dokumenNib':
+ pengajuan_tempo.dokumen_nib = [(6, 0, [sppkp_attachment.id])]
- if form_supplier_data:
- try:
- form_supplier_data = json.loads(form_supplier_data)
-
- supplier_ids_to_add = []
- for item in form_supplier_data:
- supplier_name = item.get("supplier")
- pic_name = item.get("pic")
- phone = item.get("telepon")
- tempo_duration = item.get("durasiTempo")
- credit_limit = item.get("creditLimit")
-
- new_data = {
- 'name_supplier': supplier_name,
- 'pic_name': pic_name,
- 'phone': phone,
- 'tempo_duration': tempo_duration,
- 'credit_limit': credit_limit,
- }
- new_supplier_data = request.env['user.pengajuan.tempo.line'].create(new_data)
-
- supplier_ids_to_add.append(new_supplier_data.id)
-
- pengajuan_tempo.write({'supplier_ids': [(6, 0, supplier_ids_to_add)]})
-
- except json.JSONDecodeError:
- return http.Response(status=400, json_body={'error': 'Invalid JSON format for formSupplier'})
- category_produk_ids = kw.get('categoryProduk', False)
- category_ids = ''
- if category_produk_ids:
- category_ids = list(map(int, category_produk_ids.split(',')))
- pengajuan_tempo.category_produk_ids = [(6, 0, category_ids)]
-
- tukar_invoice_input = kw.get('tukarInvoiceInput')
- if tukar_invoice_input:
- pengajuan_tempo.tukar_invoice = tukar_invoice_input
-
- tukar_invoice_input_pembayaran = kw.get('tukarInvoiceInputPembayaran')
- if tukar_invoice_input_pembayaran:
- pengajuan_tempo.jadwal_bayar = tukar_invoice_input_pembayaran
-
- dokumen_kirim = [
- 'Surat Tanda Terima Barang (STTB)',
- 'Good Receipt (GR)',
- 'Surat Terima Barang (STB)',
- 'Lembar Penerimaan Barang (LPB)'
- ]
-
- dokumen_kirim_barang_ids = kw.get('dokumenPengiriman')
- dokumen_kirim_input = kw.get('dokumenKirimInput', '')
- dokumen_kirim_barang_input = kw.get('dokumenPengirimanInput', '')
- dokumen_kirim_barang = []
-
- if dokumen_kirim_barang_ids:
- dokumen_kirim_ids = list(map(int, dokumen_kirim_barang_ids.split(',')))
- dokumen_kirim_barang = [dokumen_kirim[i] for i in dokumen_kirim_ids if 0 <= i < len(dokumen_kirim)]
- if dokumen_kirim_input:
- input_items = [item.strip() for item in dokumen_kirim_input.split(',')]
- dokumen_kirim_barang.extend(item for item in input_items if item and item not in dokumen_kirim_barang)
- pengajuan_tempo.dokumen_kirim_input = dokumen_kirim_input
- if dokumen_kirim_barang:
- pengajuan_tempo.dokumen_pengiriman = ', '.join(dokumen_kirim_barang)
- if dokumen_kirim_barang_input:
- pengajuan_tempo.dokumen_pengiriman_input = dokumen_kirim_barang_input
-
- dokumen = [
- 'Invoice Pembelian',
- 'Surat Jalan',
- 'Berita Acara Serah Terima (BAST)',
- 'Faktur Pajak',
- 'Good Receipt (GR)'
- ]
-
- dokumen_invoice = kw.get('dokumenPengirimanInvoice', '')
- if dokumen_invoice:
- pengajuan_tempo.dokumen_invoice = dokumen_invoice
- user_tempo_request = []
- if tempo_request:
- user_tempo_request = request.env['user.pengajuan.tempo.request'].create({
- 'user_id': id,
- 'pengajuan_tempo_id': pengajuan_tempo.id,
- 'user_company_id': partner_id.id,
- 'tempo_duration': pengajuan_tempo.tempo_duration.id,
- 'tempo_limit': pengajuan_tempo.tempo_limit,
- })
+ elif dokumen['documentName'] == 'dokumenSiup':
+ pengajuan_tempo.dokumen_siup = [(6, 0, [sppkp_attachment.id])]
- form_dokumen_data = kw.get('formDocs', False)
- if form_dokumen_data:
- try:
- form_dokumen = json.loads(form_dokumen_data)
+ elif dokumen['documentName'] == 'dokumenTdp':
+ pengajuan_tempo.dokumen_tdp = [(6, 0, [sppkp_attachment.id])]
- for dokumen in form_dokumen:
- if dokumen['details']['base64'] != '':
- mimetype, _ = mimetypes.guess_type(dokumen['details']['name'])
- mimetype = mimetype or 'application/octet-stream'
- data = base64.b64decode(dokumen['details']['base64'])
- sppkp_attachment = request.env['ir.attachment'].create({
- 'name': dokumen['details']['name'],
- 'type': 'binary',
- 'datas': base64.b64encode(data),
- 'res_model': 'user.pengajuan.tempo',
- 'res_id': pengajuan_tempo.id,
- 'mimetype': mimetype
- })
+ elif dokumen['documentName'] == 'dokumenSkdp':
+ pengajuan_tempo.dokumen_skdp = [(6, 0, [sppkp_attachment.id])]
- if dokumen['documentName'] == 'dokumenNib':
- pengajuan_tempo.dokumen_nib = [(6, 0, [sppkp_attachment.id])]
+ elif dokumen['documentName'] == 'dokumenSkt':
+ pengajuan_tempo.dokumen_skt = [(6, 0, [sppkp_attachment.id])]
- elif dokumen['documentName'] == 'dokumenSiup':
- pengajuan_tempo.dokumen_siup = [(6, 0, [sppkp_attachment.id])]
+ elif dokumen['documentName'] == 'dokumenNpwp':
+ pengajuan_tempo.dokumen_npwp = [(6, 0, [sppkp_attachment.id])]
- elif dokumen['documentName'] == 'dokumenTdp':
- pengajuan_tempo.dokumen_tdp = [(6, 0, [sppkp_attachment.id])]
+ elif dokumen['documentName'] == 'dokumenSppkp':
+ pengajuan_tempo.dokumen_sppkp = [(6, 0, [sppkp_attachment.id])]
- elif dokumen['documentName'] == 'dokumenSkdp':
- pengajuan_tempo.dokumen_skdp = [(6, 0, [sppkp_attachment.id])]
+ elif dokumen['documentName'] == 'dokumenAktaPerubahan':
+ pengajuan_tempo.dokumen_akta_perubahan = [(6, 0, [sppkp_attachment.id])]
- elif dokumen['documentName'] == 'dokumenSkt':
- pengajuan_tempo.dokumen_skt = [(6, 0, [sppkp_attachment.id])]
+ elif dokumen['documentName'] == 'dokumenKtpDirut':
+ pengajuan_tempo.dokumen_ktp_dirut = [(6, 0, [sppkp_attachment.id])]
- elif dokumen['documentName'] == 'dokumenNpwp':
- pengajuan_tempo.dokumen_npwp = [(6, 0, [sppkp_attachment.id])]
+ elif dokumen['documentName'] == 'dokumenAktaPendirian':
+ pengajuan_tempo.dokumen_akta_pendirian = [(6, 0, [sppkp_attachment.id])]
- elif dokumen['documentName'] == 'dokumenSppkp':
- pengajuan_tempo.dokumen_sppkp = [(6, 0, [sppkp_attachment.id])]
+ elif dokumen['documentName'] == 'dokumenLaporanKeuangan':
+ pengajuan_tempo.dokumen_laporan_keuangan = [(6, 0, [sppkp_attachment.id])]
- elif dokumen['documentName'] == 'dokumenAktaPerubahan':
- pengajuan_tempo.dokumen_akta_perubahan = [(6, 0, [sppkp_attachment.id])]
+ elif dokumen['documentName'] == 'dokumenFotoKantor':
+ pengajuan_tempo.dokumen_foto_kantor = [(6, 0, [sppkp_attachment.id])]
- elif dokumen['documentName'] == 'dokumenKtpDirut':
- pengajuan_tempo.dokumen_ktp_dirut = [(6, 0, [sppkp_attachment.id])]
+ elif dokumen['documentName'] == 'dokumenTempatBekerja':
+ pengajuan_tempo.dokumen_tempat_bekerja = [(6, 0, [sppkp_attachment.id])]
- elif dokumen['documentName'] == 'dokumenAktaPendirian':
- pengajuan_tempo.dokumen_akta_pendirian = [(6, 0, [sppkp_attachment.id])]
+ formatted_text = ''.join([' ' + char if char.isupper() and i != 0 else char for i, char in
+ enumerate(dokumen['documentName'])])
+ teks = formatted_text.strip().title()
+ pengajuan_tempo.message_post(body=teks, attachment_ids=[sppkp_attachment.id])
+ if tempo_request:
+ user_tempo_request.message_post(body=teks, attachment_ids=[sppkp_attachment.id])
- elif dokumen['documentName'] == 'dokumenLaporanKeuangan':
- pengajuan_tempo.dokumen_laporan_keuangan = [(6, 0, [sppkp_attachment.id])]
- elif dokumen['documentName'] == 'dokumenFotoKantor':
- pengajuan_tempo.dokumen_foto_kantor = [(6, 0, [sppkp_attachment.id])]
+ except json.JSONDecodeError:
+ return http.Response(status=400, json_body={'error': 'Invalid JSON format for formDokumen'})
- elif dokumen['documentName'] == 'dokumenTempatBekerja':
- pengajuan_tempo.dokumen_tempat_bekerja = [(6, 0, [sppkp_attachment.id])]
+ if tempo_request:
+ # pengajuan_tempo.user_id = id
+ template = pengajuan_tempo.env.ref('indoteknik_custom.mail_template_res_user_company_request_tempo_review')
+ template.send_mail(pengajuan_tempo.id, force_send=True)
+ template2 = pengajuan_tempo.env.ref('indoteknik_custom.mail_template_res_user_company_new_tempo_to_sales')
+ template2.send_mail(pengajuan_tempo.id, force_send=True)
+ if not pengajuan_tempo:
+ return self.response(code=500, description="Failed to create or update pengajuan_tempo")
- formatted_text = ''.join([' ' + char if char.isupper() and i != 0 else char for i, char in
- enumerate(dokumen['documentName'])])
- teks = formatted_text.strip().title()
- pengajuan_tempo.message_post(body=teks, attachment_ids=[sppkp_attachment.id])
- if tempo_request:
- user_tempo_request.message_post(body=teks, attachment_ids=[sppkp_attachment.id])
-
-
- except json.JSONDecodeError:
- return http.Response(status=400, json_body={'error': 'Invalid JSON format for formDokumen'})
+ return self.response({
+ 'id': pengajuan_tempo.id,
+ 'user_id': user_id,
+ })
+ except Exception as e:
+ return self.response(code=500, description=f'Unexpected error: {str(e)}')
- if tempo_request:
- # pengajuan_tempo.user_id = id
- template = pengajuan_tempo.env.ref('indoteknik_custom.mail_template_res_user_company_request_tempo_review')
- template.send_mail(pengajuan_tempo.id, force_send=True)
- template2 = pengajuan_tempo.env.ref('indoteknik_custom.mail_template_res_user_company_new_tempo_to_sales')
- template2.send_mail(pengajuan_tempo.id, force_send=True)
- return self.response({
- 'id': pengajuan_tempo.id,
- 'user_id': user_id,
- })
def get_user_by_email(self, email):
return request.env['res.users'].search([
diff --git a/indoteknik_api/controllers/api_v1/product.py b/indoteknik_api/controllers/api_v1/product.py
index 32362582..557215ea 100644
--- a/indoteknik_api/controllers/api_v1/product.py
+++ b/indoteknik_api/controllers/api_v1/product.py
@@ -1,13 +1,13 @@
from .. import controller
from odoo import http
-from odoo.http import request
+from odoo.http import request, Response
from datetime import datetime, timedelta
import ast
import logging
import math
import json
-_logger = logging.getLogger(__name__)
+_logger = logging.getLogger(__name__)
class Product(controller.Controller):
@@ -33,9 +33,64 @@ class Product(controller.Controller):
categories.reverse()
return self.response(categories, headers=[('Cache-Control', 'max-age=3600, public')])
-
- @http.route(prefix + 'product_variant/<id>/stock', auth='public', methods=['GET', 'OPTIONS'])
+
+ @http.route(prefix + 'product/variants/sla', auth='public', methods=['GET', 'OPTIONS'])
@controller.Controller.must_authorized()
+ def get_product_template_sla_by_id(self, **kwargs):
+ body_params = kwargs.get('ids')
+
+ if not body_params:
+ return self.response('Failed', code=400, description='id is required')
+
+ ids = [int(id.strip()) for id in body_params.split(',') if id.strip().isdigit()]
+
+ sla_duration = 0
+ sla_unit = 'Hari'
+ include_instant = True
+ products = request.env['product.product'].search([('id', 'in', ids)])
+ if len(products) < 1:
+ return self.response(
+ 'Failed',
+ code=400,
+ description='Produk Tidak Di Temukan.'
+ )
+
+ product_slas = request.env['product.sla'].search([('product_variant_id', 'in', ids)])
+ if len(product_slas) < 1:
+ return self.response(
+ 'Failed',
+ code=400,
+ description='SLA Tidak Di Temukan.'
+ )
+
+ # Mapping SLA untuk mempermudah lookup
+ sla_map = {sla.product_variant_id.id: sla for sla in product_slas}
+
+ for product in products:
+ product_sla = sla_map.get(product.id)
+ if product_sla:
+ sla_duration = max(sla_duration, int(product_sla.sla))
+ sla_unit = product_sla.sla_vendor_id.unit
+ if product.qty_free_bandengan < 1 :
+ if product_sla.sla_vendor_id.unit != 'jam':
+ include_instant = False
+ break
+
+ start_date = datetime.today().date()
+ additional_days = request.env['sale.order'].get_days_until_next_business_day(start_date)
+
+ # Jika semua loop selesai tanpa include_instant menjadi False
+ return self.response({
+ 'include_instant': include_instant,
+ 'sla_duration': sla_duration,
+ 'sla_additional_days': additional_days,
+ 'sla_total' : int(sla_duration) + int(additional_days),
+ 'sla_unit': 'Hari' if additional_days > 0 else sla_unit
+ }
+ )
+
+ @http.route(prefix + 'product_variant/<id>/stock', auth='public', methods=['GET', 'OPTIONS'])
+ @controller.Controller.must_authorized()
def get_product_template_stock_by_id(self, **kw):
id = int(kw.get('id'))
date_7_days_ago = datetime.now() - timedelta(days=7)
@@ -49,10 +104,11 @@ class Product(controller.Controller):
], limit=1)
qty_available = product.qty_free_bandengan
-
- if qty_available < 0:
- qty_available = 0
-
+
+
+ if qty_available < 1 :
+ qty_available = 0
+
qty = 0
sla_date = '-'
@@ -74,24 +130,25 @@ class Product(controller.Controller):
if qty_available > 0:
qty = qty_available + total_adem + total_excell
+ sla_date = product_sla.sla or 1
elif qty_altama > 0 or qty_vendor > 0:
qty = total_adem if qty_altama > 0 else total_excell
- sla_date = '2-4 Hari'
+ sla_date = product_sla.sla
else:
- sla_date = '3-7 Hari'
+ sla_date = product_sla.sla
except:
print('error')
else:
if qty_available > 0:
qty = qty_available
- sla_date = product_sla.sla or '-'
+ sla_date = product_sla.sla or 'Indent'
elif qty_vendor > 0:
qty = total_excell
sla_date = '2-4 Hari'
data = {
'qty': qty,
- 'sla_date': sla_date,
+ 'sla_date': sla_date
}
return self.response(data, headers=[('Cache-Control', 'max-age=600, private')])
diff --git a/indoteknik_api/controllers/api_v1/sale_order.py b/indoteknik_api/controllers/api_v1/sale_order.py
index a7e027c8..6815bf6c 100644
--- a/indoteknik_api/controllers/api_v1/sale_order.py
+++ b/indoteknik_api/controllers/api_v1/sale_order.py
@@ -386,7 +386,8 @@ class SaleOrder(controller.Controller):
'note_website': [],
'voucher': [],
'source': [],
- 'estimated_arrival_days': ['number', 'default:0']
+ 'estimated_arrival_days': ['number', 'default:0'],
+ 'estimated_arrival_days_start': ['number', 'default:0']
})
if not params['valid']:
@@ -417,6 +418,7 @@ class SaleOrder(controller.Controller):
'partner_purchase_order_file': params['value']['po_file'],
'delivery_amt': params['value']['delivery_amount'] * 1.10,
'estimated_arrival_days': params['value']['estimated_arrival_days'],
+ 'estimated_arrival_days_start': params['value']['estimated_arrival_days_start'],
'shipping_cost_covered': 'customer',
'shipping_paid_by': 'customer',
'carrier_id': params['value']['carrier_id'],
diff --git a/indoteknik_api/controllers/api_v1/stock_picking.py b/indoteknik_api/controllers/api_v1/stock_picking.py
index 2e0c4ad0..55e07152 100644
--- a/indoteknik_api/controllers/api_v1/stock_picking.py
+++ b/indoteknik_api/controllers/api_v1/stock_picking.py
@@ -101,7 +101,7 @@ class StockPicking(controller.Controller):
picking = picking_model.browse(id)
if not picking:
return self.response(None)
-
+ hostori = picking.get_tracking_detail()
return self.response(picking.get_tracking_detail())
@http.route(prefix + 'stock-picking/<id>/tracking', auth='public', method=['GET', 'OPTIONS'])
@@ -136,4 +136,50 @@ class StockPicking(controller.Controller):
return self.response({
'name': picking_data.name
- }) \ No newline at end of file
+ })
+
+ @http.route(prefix + 'webhook/biteship', type='json', auth='public', methods=['POST'], csrf=False)
+ def udpate_status_from_bitehsip(self, **kw):
+ try:
+ if not request.jsonrequest:
+ return "ok"
+
+ data = request.jsonrequest # Ambil data JSON dari request
+ event = data.get('event')
+
+ # Handle Event Berdasarkan Jenisnya
+ if event == "order.status":
+ self.process_order_status(data)
+ elif event == "order.price":
+ self.process_order_price(data)
+ elif event == "order.waybill_id":
+ self.process_order_waybill(data)
+
+ return {'success': True, 'message': f'Webhook {event} received'}
+ except Exception as e:
+ return {'success': False, 'message': str(e)}
+
+ def process_order_status(self, data):
+ picking_model = request.env['stock.picking'].sudo().search([('biteship_id', '=', data.get('order_id'))], limit=1)
+ if data.get('status') == 'picked':
+ picking_model.write({'driver_departure_date': datetime.utcnow()})
+ elif data.get('status') == 'delivered':
+ picking_model.write({'driver_arrival_date': datetime.utcnow()})
+
+ def process_order_price(self, data):
+ picking_model = request.env['stock.picking'].sudo().search([('biteship_id', '=', data.get('order_id'))], limit=1)
+ order = request.env['sale.order'].sudo().search([('name', '=', picking_model.sale_id.name)], limit=1)
+ if order:
+ order.write({
+ 'delivery_amt': data.get('price')
+ })
+
+ def process_order_waybill(self, data):
+ picking_model = request.env['stock.picking'].sudo().search([('biteship_id', '=', data.get('order_id'))], limit=1)
+ if picking_model:
+ picking_model.write({
+ 'biteship_waybill_id': data.get('courier_waybill_id'),
+ 'delivery_tracking_no': data.get('courier_waybill_id'),
+ 'biteship_tracking_id':data.get('courier_tracking_id')
+ })
+ \ No newline at end of file
diff --git a/indoteknik_api/models/res_partner.py b/indoteknik_api/models/res_partner.py
index 0e09fbc6..3a98f3bc 100644
--- a/indoteknik_api/models/res_partner.py
+++ b/indoteknik_api/models/res_partner.py
@@ -59,6 +59,7 @@ class ResPartner(models.Model):
# Pengiriman
'PIC_tittle' : pengajuan_tempo.pic_tittle if pengajuan_tempo.pic_tittle else '',
+ 'PICBarangMobile' : pengajuan_tempo.pic_mobile if pengajuan_tempo.pic_mobile else '',
'PIC_name' : pengajuan_tempo.pic_name if pengajuan_tempo.pic_name else '',
'street_pengiriman' : pengajuan_tempo.street_pengiriman if pengajuan_tempo.street_pengiriman else '',
'state_pengiriman' : pengajuan_tempo.state_id_pengiriman.id if pengajuan_tempo.state_id_pengiriman else '',
@@ -67,6 +68,7 @@ class ResPartner(models.Model):
'subDistrict_pengiriman': pengajuan_tempo.subDistrict_id_pengiriman.id if pengajuan_tempo.subDistrict_id_pengiriman else '',
'zip_pengiriman' : pengajuan_tempo.zip_pengiriman if pengajuan_tempo.zip_pengiriman else '',
'invoice_pic_tittle' : pengajuan_tempo.invoice_pic_tittle if pengajuan_tempo.invoice_pic_tittle else '',
+ 'invoice_pic_mobile' : pengajuan_tempo.invoice_pic_mobile if pengajuan_tempo.invoice_pic_mobile else '',
'invoice_pic' : pengajuan_tempo.invoice_pic if pengajuan_tempo.invoice_pic else '',
'street_invoice' : pengajuan_tempo.street_invoice if pengajuan_tempo.street_invoice else '',
'state_invoice' : pengajuan_tempo.state_id_invoice.id if pengajuan_tempo.state_id_invoice else '',
@@ -82,6 +84,12 @@ class ResPartner(models.Model):
'dokumen_pengiriman_invoice' : pengajuan_tempo.dokumen_invoice if pengajuan_tempo.dokumen_invoice else '',
'is_same_addrees': pengajuan_tempo.is_same_address if pengajuan_tempo.is_same_address else False,
'is_same_addrees_street': pengajuan_tempo.is_same_address_street if pengajuan_tempo.is_same_address_street else False,
+ 'dokumen_prosedur':
+ {
+ 'name': pengajuan_tempo.dokumen_prosedur.name,
+ 'base64': pengajuan_tempo.dokumen_prosedur.datas.decode('utf-8'),
+ 'format': pengajuan_tempo.dokumen_prosedur.mimetype,
+ } if pengajuan_tempo.dokumen_prosedur else '',
'supplier_ids': [
{
'id': supplier.id,
diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py
index f66314fa..a7096346 100755
--- a/indoteknik_custom/__manifest__.py
+++ b/indoteknik_custom/__manifest__.py
@@ -161,7 +161,9 @@
'report/report_invoice.xml',
'report/report_picking.xml',
'report/report_sale_order.xml',
+ 'views/vendor_sla.xml',
'views/coretax_faktur.xml',
+ 'views/public_holiday.xml',
'views/stock_inventory.xml',
],
'demo': [],
diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py
index 3573eddd..37a49332 100755
--- a/indoteknik_custom/models/__init__.py
+++ b/indoteknik_custom/models/__init__.py
@@ -139,8 +139,10 @@ from . import user_pengajuan_tempo
from . import approval_retur_picking
from . import va_multi_approve
from . import va_multi_reject
+from . import vendor_sla
from . import stock_immediate_transfer
from . import coretax_fatur
+from . import public_holiday
from . import ir_actions_report
from . import barcoding_product
from . import account_payment_register
diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py
index 9aa0743b..45fdb8df 100644
--- a/indoteknik_custom/models/account_move.py
+++ b/indoteknik_custom/models/account_move.py
@@ -76,7 +76,8 @@ class AccountMove(models.Model):
def compute_other_taxes(self):
for rec in self:
- rec.other_taxes = rec.other_subtotal * 0.12
+ rec.other_taxes = round(rec.other_subtotal * 0.12, 2)
+
def compute_other_subtotal(self):
for rec in self:
diff --git a/indoteknik_custom/models/commision.py b/indoteknik_custom/models/commision.py
index 6920154a..0d31e954 100644
--- a/indoteknik_custom/models/commision.py
+++ b/indoteknik_custom/models/commision.py
@@ -157,7 +157,22 @@ class CustomerCommision(models.Model):
('pending', 'Pending'),
('payment', 'Payment'),
], string='Payment Status', copy=False, readonly=True, tracking=3, default='pending')
+ grouped_so_number = fields.Char(string='Group SO Number', compute='_compute_grouped_numbers')
+ grouped_invoice_number = fields.Char(string='Group Invoice Number', compute='_compute_grouped_numbers')
+ def _compute_grouped_numbers(self):
+ for rec in self:
+ so_numbers = set()
+ invoice_numbers = set()
+
+ for line in rec.commision_lines:
+ if line.invoice_id:
+ if line.invoice_id.sale_id:
+ so_numbers.add(line.invoice_id.sale_id.name)
+ invoice_numbers.add(line.invoice_id.name)
+
+ rec.grouped_so_number = ', '.join(sorted(so_numbers))
+ rec.grouped_invoice_number = ', '.join(sorted(invoice_numbers))
# add status for type of commision, fee, rebate / cashback
# include child or not?
diff --git a/indoteknik_custom/models/product_sla.py b/indoteknik_custom/models/product_sla.py
index 2e663d30..04ad2ffd 100644
--- a/indoteknik_custom/models/product_sla.py
+++ b/indoteknik_custom/models/product_sla.py
@@ -12,73 +12,110 @@ class ProductSla(models.Model):
_rec_name = 'product_variant_id'
product_variant_id = fields.Many2one('product.product',string='Product')
- avg_leadtime = fields.Char(string='AVG Leadtime', readonly=True)
- leadtime = fields.Char(string='Leadtime', readonly=True)
+ sla_vendor_id = fields.Many2one('vendor.sla',string='Vendor', readonly=True)
+ sla_vendor_duration = fields.Char(string='AVG Leadtime', related='sla_vendor_id.duration_unit')
+ sla_logistic = fields.Char(string='SLA Logistic', readonly=True)
+ sla_logistic_unit = fields.Selection(
+ [('jam', 'Jam'),('hari', 'Hari')],
+ string="SLA Logistic Time"
+ )
+ sla_logistic_duration_unit = fields.Char(string="SLA Logistic Duration (Unit)")
sla = fields.Char(string='SLA', readonly=True)
version = fields.Integer(string="Version", compute="_compute_version")
def _compute_version(self):
for sla in self:
sla.version = sla.product_variant_id.sla_version
+
+
- def generate_product_variant_id_sla(self, limit=5000):
- # Filter produk non-Altama
+ def generate_product_variant_id_sla(self, limit=500):
+ offset = 0
+ # while True:
products = self.env['product.product'].search([
- ('x_manufacture', 'not in', [10, 122, 89]),
- ('location_id', '=', 57),
- ('stock_move_ids', '!=', False),
- ], order='sla_version asc', limit=limit)
+ ('active', '=', True),
+ ('sale_ok', '=', True),
+ ], order='sla_version asc', limit=limit, offset=offset)
+
+ # if not products:
+ # break
- i = 1
for product in products:
- _logger.info(f'Product SLA: {i}/{len(products)}')
- i += 1
- product.sla_version += 1
+ _logger.info(f'Memproses SLA untuk produk ID {product.id}, versi {product.sla_version}')
product_sla = self.search([('product_variant_id', '=', product.id)], limit=1)
if not product_sla:
- product_sla = self.env['product.sla'].create({
- 'product_variant_id': product.id,
- })
-
+ product_sla = self.create({'product_variant_id': product.id})
+
product_sla.generate_product_sla()
+ # Tandai produk sebagai sudah diproses
+ product.sla_version += 1
+
+ offset += limit
+
+
def generate_product_sla(self):
- self.avg_leadtime = '-'
- self.sla = '-'
+ # self.sla_logistic = '-'
+ # self.sla_logistic_duration_unit = '-'
+ # self.sla = '-'
product = self.product_variant_id
-
- qty_available = 0
- qty_available = product.qty_onhand_bandengan
+
+ q_vendor = [
+ ('product_id', '=', product.id),
+ ('is_winner', '=', True)
+ ]
+
+ vendor = self.env['purchase.pricelist'].search(q_vendor)
+ vendor_duration = 0
- if qty_available > 0:
- self.sla = '1 Hari'
+ #SLA Vendor
+ if vendor:
+ vendor_sla = self.env['vendor.sla'].search([('id_vendor', '=', vendor.vendor_id.id)], limit=1)
+ sla_vendor = int(vendor_sla.duration) if vendor_sla else 0
+ if sla_vendor > 0:
+ if vendor_sla.unit == 'hari':
+ vendor_duration = vendor_sla.duration * 24 * 60
+ else :
+ vendor_duration = vendor_sla.duration * 60
+
+ self.sla_vendor_id = vendor_sla.id if vendor_sla else False
+ #SLA Logistik selalu 1 hari
+ estimation_sla = (1 * 24 * 60) + vendor_duration
+ estimation_sla_days = estimation_sla / (24 * 60)
+ self.sla = math.ceil(estimation_sla_days)
+ self.sla_logistic = int(1)
+ self.sla_logistic_unit = 'hari'
+ self.sla_logistic_duration_unit = '1 hari'
+ else:
+ self.unlink()
+ else:
+ self.unlink()
+
- query = [
- ('product_id', '=', product.id),
- ('picking_id', '!=', False),
- ('picking_id.location_id', '=', 57),
- ('picking_id.state', 'not in', ['cancel'])
- ]
- picking = self.env['stock.move.line'].search(query)
- leadtimes=[]
- for stock in picking:
- date_delivered = stock.picking_id.driver_departure_date
- date_so_confirmed = stock.picking_id.sale_id.date_order
- if date_delivered and date_so_confirmed:
- leadtime = date_delivered - date_so_confirmed
- leadtime_in_days = leadtime.days
- leadtimes.append(leadtime_in_days)
+ # query = [
+ # ('product_id', '=', product.id),
+ # ('picking_id', '!=', False),
+ # ('picking_id.location_id', '=', 57),
+ # ('picking_id.state', 'not in', ['cancel'])
+ # ]
+ # picking = self.env['stock.move.line'].search(query)
+
+ # leadtimes=[]
+ # for stock in picking:
+ # date_delivered = stock.picking_id.driver_departure_date
+ # date_do_ready = stock.picking_id.date_reserved
+ # if date_delivered and date_do_ready:
+ # leadtime = date_delivered - date_do_ready
+ # leadtime_in_days = leadtime.days
+ # leadtimes.append(leadtime_in_days)
- if len(leadtimes) > 0:
- avg_leadtime = sum(leadtimes) / len(leadtimes)
- rounded_leadtime = math.ceil(avg_leadtime)
- self.avg_leadtime = rounded_leadtime
- if rounded_leadtime >= 1 and rounded_leadtime <= 5:
- self.sla = '3-7 Hari'
- elif rounded_leadtime >= 6 and rounded_leadtime <= 10:
- self.sla = '4-12 Hari'
- elif rounded_leadtime >= 11:
- self.sla = 'Indent' \ No newline at end of file
+ # if len(leadtimes) > 0:
+ # avg_leadtime = sum(leadtimes) / len(leadtimes)
+ # rounded_leadtime = math.ceil(avg_leadtime)
+ # estimation_sla = ((rounded_leadtime * 24 * 60) + vendor_duration)/2
+ # estimation_sla_days = estimation_sla / (24 * 60)
+ # self.sla = math.ceil(estimation_sla_days)
+ # self.avg_leadtime = int(rounded_leadtime) \ No newline at end of file
diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py
index efacb95f..600dd90e 100755
--- a/indoteknik_custom/models/product_template.py
+++ b/indoteknik_custom/models/product_template.py
@@ -422,6 +422,18 @@ class ProductProduct(models.Model):
merchandise_ok = fields.Boolean(string='Product Promotion')
qr_code_variant = fields.Binary("QR Code Variant", compute='_compute_qr_code_variant')
+ def generate_product_sla(self):
+ product_variant_ids = self.env.context.get('active_ids', [])
+ product_variant = self.search([('id', 'in', product_variant_ids)])
+ sla_record = self.env['product.sla'].search([('product_variant_id', '=', product_variant.id)], limit=1)
+
+ if sla_record:
+ sla_record.generate_product_sla()
+ else:
+ new_sla_record = self.env['product.sla'].create({
+ 'product_variant_id': product_variant.id,
+ })
+ new_sla_record.generate_product_sla()
@api.model
def create(self, vals):
group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id
diff --git a/indoteknik_custom/models/public_holiday.py b/indoteknik_custom/models/public_holiday.py
new file mode 100644
index 00000000..851d9080
--- /dev/null
+++ b/indoteknik_custom/models/public_holiday.py
@@ -0,0 +1,11 @@
+from odoo import api, fields, models
+from datetime import timedelta, datetime
+
+class PublicHoliday(models.Model):
+ _name = 'hr.public.holiday'
+ _description = 'Public Holidays'
+
+ name = fields.Char(string='Holiday Name', required=True)
+ start_date = fields.Date('Date Holiday', required=True)
+ # end_date = fields.Date('End Holiday Date', required=True)
+ # company_id = fields.Many2one('res.company', 'Company')
diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py
index 7e574a72..fd3a0514 100644
--- a/indoteknik_custom/models/res_partner.py
+++ b/indoteknik_custom/models/res_partner.py
@@ -58,6 +58,7 @@ class ResPartner(models.Model):
# Pengiriman
pic_name = fields.Char(string='Nama PIC Penerimaan Barang')
+ pic_mobile = fields.Char(string='Nomor HP PIC Penerimaan Barang')
street_pengiriman = fields.Char(string="Alamat Perusahaan")
state_id_pengiriman = fields.Many2one('res.country.state', string='State')
city_id_pengiriman = fields.Many2one('vit.kota', string='City')
@@ -65,6 +66,7 @@ class ResPartner(models.Model):
subDistrict_id_pengiriman = fields.Many2one('vit.kelurahan', string='Kelurahan')
zip_pengiriman = fields.Char(string="Zip")
invoice_pic = fields.Char(string='Nama PIC Penerimaan Invoice')
+ invoice_pic_mobile = fields.Char(string='Nomor HP PIC Penerimaan Invoice')
street_invoice = fields.Char(string="Alamat Perusahaan")
state_id_invoice = fields.Many2one('res.country.state', string='State')
city_id_invoice = fields.Many2one('vit.kota', string='City')
@@ -73,6 +75,7 @@ class ResPartner(models.Model):
zip_invoice = fields.Char(string="Zip")
tukar_invoice = fields.Char(string='Jadwal Penukaran Invoice')
jadwal_bayar = fields.Char(string='Jadwal Pembayaran')
+ dokumen_prosedur = fields.Many2one('ir.attachment', string="Dokumen Pengiriman", tracking=3, readonly=True)
dokumen_pengiriman = fields.Char(string='Dokumen Tanda Terima yang Diberikan Pada Saat Pengiriman Barang')
dokumen_pengiriman_input = fields.Char(string='Dokumen yang Dibawa Saat Pengiriman Barang')
dokumen_invoice = fields.Char(string='Dokumen yang dilampirkan saat Pengiriman Invoice')
@@ -124,8 +127,8 @@ class ResPartner(models.Model):
('PNR', 'Pareto Non Repeating'),
('NP', 'Non Pareto')
])
- email_finance = fields.Char(string='Email Finance')
- email_sales = fields.Char(string='Email Sales')
+ email_finance = fields.Char(string='Email Finance Vendor')
+ email_sales = fields.Char(string='Email Sales Vendor')
user_payment_terms_sales = fields.Many2one('res.users', string='Users Update Payment Terms')
date_payment_terms_sales = fields.Datetime(string='Date Update Payment Terms')
@@ -188,10 +191,10 @@ class ResPartner(models.Model):
def _check_duplicate_name(self):
for record in self:
if record.name:
- # Mencari partner lain yang memiliki nama sama (case-insensitive)
existing_partner = self.env['res.partner'].search([
- ('id', '!=', record.id), # Hindari mencocokkan diri sendiri
- ('name', '=', record.name) # Case-insensitive search
+ ('id', '!=', record.id),
+ ('name', '=', record.name),
+ ('email', '=', record.email)
], limit=1)
if existing_partner:
@@ -254,6 +257,7 @@ class ResPartner(models.Model):
# Pengiriman
vals['pic_name'] = vals.get('pic_name', self.pic_name)
+ vals['pic_mobile'] = vals.get('pic_mobile', self.pic_mobile)
vals['street_pengiriman'] = vals.get('street_pengiriman', self.street_pengiriman)
vals['state_id_pengiriman'] = vals.get('state_id_pengiriman', self.state_id_pengiriman)
vals['city_id_pengiriman'] = vals.get('city_id_pengiriman', self.city_id_pengiriman)
@@ -261,6 +265,7 @@ class ResPartner(models.Model):
vals['subDistrict_id_pengiriman'] = vals.get('subDistrict_id_pengiriman', self.subDistrict_id_pengiriman)
vals['zip_pengiriman'] = vals.get('zip_pengiriman', self.zip_pengiriman)
vals['invoice_pic'] = vals.get('invoice_pic', self.invoice_pic)
+ vals['invoice_pic_mobile'] = vals.get('invoice_pic_mobile', self.invoice_pic_mobile)
vals['street_invoice'] = vals.get('street_invoice', self.street_invoice)
vals['state_id_invoice'] = vals.get('state_id_invoice', self.state_id_invoice)
vals['city_id_invoice'] = vals.get('city_id_invoice', self.city_id_invoice)
@@ -269,6 +274,7 @@ class ResPartner(models.Model):
vals['zip_invoice'] = vals.get('zip_invoice', self.zip_invoice)
vals['tukar_invoice'] = vals.get('tukar_invoice', self.tukar_invoice)
vals['jadwal_bayar'] = vals.get('jadwal_bayar', self.jadwal_bayar)
+ vals['dokumen_prosedur'] = vals.get('dokumen_prosedur', self.dokumen_prosedur)
vals['dokumen_pengiriman'] = vals.get('dokumen_pengiriman', self.dokumen_pengiriman)
vals['dokumen_pengiriman_input'] = vals.get('dokumen_pengiriman_input', self.dokumen_pengiriman_input)
vals['dokumen_invoice'] = vals.get('dokumen_invoice', self.dokumen_invoice)
@@ -323,6 +329,7 @@ class ResPartner(models.Model):
'finance_mobile': vals.get('finance_mobile'),
'finance_email': vals.get('finance_email'),
'pic_name': vals.get('pic_name'),
+ 'pic_mobile': vals.get('pic_mobile'),
'street_pengiriman': vals.get('street_pengiriman'),
'state_id_pengiriman': vals.get('state_id_pengiriman'),
'city_id_pengiriman': vals.get('city_id_pengiriman'),
@@ -330,6 +337,7 @@ class ResPartner(models.Model):
'subDistrict_id_pengiriman': vals.get('subDistrict_id_pengiriman'),
'zip_pengiriman': vals.get('zip_pengiriman'),
'invoice_pic': vals.get('invoice_pic'),
+ 'invoice_pic_mobile': vals.get('invoice_pic_mobile'),
'street_invoice': vals.get('street_invoice'),
'state_id_invoice': vals.get('state_id_invoice'),
'city_id_invoice': vals.get('city_id_invoice'),
@@ -338,6 +346,7 @@ class ResPartner(models.Model):
'zip_invoice': vals.get('zip_invoice'),
'tukar_invoice': vals.get('tukar_invoice'),
'jadwal_bayar': vals.get('jadwal_bayar'),
+ 'dokumen_prosedur': vals.get('dokumen_prosedur'),
'dokumen_pengiriman': vals.get('dokumen_pengiriman'),
'dokumen_pengiriman_input': vals.get('dokumen_pengiriman_input'),
'dokumen_invoice': vals.get('dokumen_invoice'),
@@ -470,4 +479,9 @@ class ResPartner(models.Model):
if not self.nitku.isdigit():
raise UserError("NITKU harus berupa angka.")
if len(self.nitku) != 22:
- raise UserError("NITKU harus memiliki tepat 22 angka.") \ No newline at end of file
+ raise UserError("NITKU harus memiliki tepat 22 angka.")
+
+ @api.onchange('name')
+ def _onchange_name(self):
+ if self.company_type == 'person':
+ self.nama_wajib_pajak = self.name \ No newline at end of file
diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py
index 8a983479..14a8e688 100755
--- a/indoteknik_custom/models/sale_order.py
+++ b/indoteknik_custom/models/sale_order.py
@@ -51,6 +51,16 @@ class CancelReasonOrder(models.TransientModel):
order.confirm_cancel_order()
return {'type': 'ir.actions.act_window_close'}
+
+class ShippingOption(models.Model):
+ _name = "shipping.option"
+ _description = "Shipping Option"
+
+ name = fields.Char(string="Option Name", required=True)
+ price = fields.Float(string="Price", required=True)
+ provider = fields.Char(string="Provider")
+ etd = fields.Char(string="Estimated Delivery Time")
+ sale_order_id = fields.Many2one('sale.order', string="Sale Order", ondelete="cascade")
class SaleOrder(models.Model):
_inherit = "sale.order"
@@ -137,11 +147,13 @@ class SaleOrder(models.Model):
applied_voucher_shipping_id = fields.Many2one(comodel_name='voucher', string='Applied Voucher', copy=False)
amount_voucher_shipping_disc = fields.Float(string='Voucher Discount')
source_id = fields.Many2one('utm.source', 'Source', domain="[('id', 'in', [32, 59, 60, 61])]", required=True)
- estimated_arrival_days = fields.Integer('Estimated Arrival Days', default=0)
+ estimated_arrival_days = fields.Integer('Estimated Arrival To', default=0)
+ estimated_arrival_days_start = fields.Integer('Estimated Arrival From', default=0)
email = fields.Char(string='Email')
picking_iu_id = fields.Many2one('stock.picking', 'Picking IU')
helper_by_id = fields.Many2one('res.users', 'Helper By')
- eta_date = fields.Datetime(string='ETA Date', copy=False, compute='_compute_eta_date')
+ eta_date_start = fields.Datetime(string='ETA Date start', copy=False, compute='_compute_eta_date')
+ eta_date = fields.Datetime(string='ETA Date end', copy=False, compute='_compute_eta_date')
flash_sale = fields.Boolean(string='Flash Sale', help='Data dari web')
is_continue_transaction = fields.Boolean(string='Button Transaction', help='Data dari web')
web_approval = fields.Selection([
@@ -188,7 +200,17 @@ class SaleOrder(models.Model):
('PNR', 'Pareto Non Repeating'),
('NP', 'Non Pareto')
])
+ estimated_ready_ship_date = fields.Datetime(
+ string='ET Ready to Ship compute',
+ compute='_compute_etrts_date'
+ )
+ expected_ready_to_ship = fields.Datetime(
+ string='ET Ready to Ship',
+ copy=False,
+ store=True
+ )
shipping_method_picking = fields.Char(string='Shipping Method Picking', compute='_compute_shipping_method_picking')
+
reason_cancel = fields.Selection([
('harga_terlalu_mahal', 'Harga barang terlalu mahal'),
('harga_web_tidak_valid', 'Harga web tidak valid'),
@@ -202,12 +224,19 @@ class SaleOrder(models.Model):
('dokumen_tidak_support', 'Indoteknik tidak bisa support document yang dibutuhkan (Ex: TKDN, COO, SNI)'),
('ganti_quotation', 'Ganti Quotation'),
('testing_internal', 'Testing Internal'),
+ ('revisi_data', 'Revisi Data'),
], string='Reason for Cancel', copy=False, index=True, tracking=3)
attachment_bukti = fields.Many2one(
'ir.attachment',
string="Attachment Bukti Cancel", readonly=False,
)
nomor_so_pengganti = fields.Char(string='Nomor SO Pengganti', copy=False, tracking=3)
+ shipping_option_id = fields.Many2one("shipping.option", string="Selected Shipping Option", domain="['|', ('sale_order_id', '=', False), ('sale_order_id', '=', id)]")
+
+ @api.constrains('shipping_option_id')
+ def _check_shipping_option(self):
+ for rec in self:
+ rec.delivery_amt = rec.shipping_option_id.price
def _compute_shipping_method_picking(self):
for order in self:
@@ -255,15 +284,26 @@ class SaleOrder(models.Model):
if total_weight == 0:
raise UserError("Tidak dapat mengestimasi ongkir tanpa berat yang valid.")
-
+
if total_weight < 10:
total_weight = 10
+
self.delivery_amt = total_weight * 3000
+
+ shipping_option = self.env["shipping.option"].create({
+ "name": "Indoteknik Delivery",
+ "price": self.delivery_amt,
+ "provider": "Indoteknik",
+ "etd": "1-2 Hari",
+ "sale_order_id": self.id,
+ })
+ self.shipping_option_id = shipping_option.id
def action_estimate_shipping(self):
if self.carrier_id.id in [1, 151]:
self.action_indoteknik_estimate_shipping()
return
+
total_weight = 0
missing_weight_products = []
@@ -281,33 +321,35 @@ class SaleOrder(models.Model):
if total_weight == 0:
raise UserError("Tidak dapat mengestimasi ongkir tanpa berat yang valid.")
- # Mendapatkan city_id berdasarkan nama kota
- origin_city_name = self.warehouse_id.partner_id.kota_id.name
destination_subsdistrict_id = self.real_shipping_id.kecamatan_id.rajaongkir_id
-
if not destination_subsdistrict_id:
- raise UserError("Gagal mendapatkan ID kota asal atau tujuan.")
+ raise UserError("Gagal mendapatkan ID kota tujuan.")
result = self._call_rajaongkir_api(total_weight, destination_subsdistrict_id)
if result:
- estimated_cost = result['rajaongkir']['results'][0]['costs'][0]['cost'][0]['value']
- self.delivery_amt = estimated_cost
-
- shipping_info = []
+ shipping_options = []
for courier in result['rajaongkir']['results']:
for cost_detail in courier['costs']:
service = cost_detail['service']
description = cost_detail['description']
etd = cost_detail['cost'][0]['etd']
value = cost_detail['cost'][0]['value']
- shipping_info.append(f"Service: {service}, Description: {description}, ETD: {etd} hari, Cost: Rp {value}")
+ shipping_options.append((service, description, etd, value, courier['code']))
+
+ self.env["shipping.option"].search([('sale_order_id', '=', self.id)]).unlink()
+
+ for service, description, etd, value, provider in shipping_options:
+ self.env["shipping.option"].create({
+ "name": service,
+ "price": value,
+ "provider": provider,
+ "etd": etd,
+ "sale_order_id": self.id,
+ })
- log_message = "<br/>".join(shipping_info)
+ self.shipping_option_id = self.env["shipping.option"].search([('sale_order_id', '=', self.id)], limit=1).id
- description_ongkir = result['rajaongkir']['results'][0]['costs'][0]['description']
- etd_ongkir = result['rajaongkir']['results'][0]['costs'][0]['cost'][0]['etd']
- service_ongkir = result['rajaongkir']['results'][0]['costs'][0]['service']
- self.message_post(body=f"Estimasi Ongkos Kirim: Rp{self.delivery_amt}<br/>Service: {service_ongkir}<br/>Description: {description_ongkir}<br/>ETD: {etd_ongkir}<br/>Detail Lain:<br/>{log_message}")
+ self.message_post(body=f"Estimasi Ongkos Kirim: Rp{self.delivery_amt}<br/>Detail Lain:<br/>{'<br/>'.join([f'Service: {s[0]}, Description: {s[1]}, ETD: {s[2]} hari, Cost: Rp {s[3]}' for s in shipping_options])}")
else:
raise UserError("Gagal mendapatkan estimasi ongkir.")
@@ -435,19 +477,126 @@ class SaleOrder(models.Model):
rec.compute_fullfillment = True
+ @api.depends('date_order', 'estimated_arrival_days', 'state', 'estimated_arrival_days_start')
def _compute_eta_date(self):
- max_leadtime = 0
+ for rec in self:
+ if rec.date_order and rec.state not in ['cancel'] and rec.estimated_arrival_days and rec.estimated_arrival_days_start:
+ rec.eta_date = rec.date_order + timedelta(days=rec.estimated_arrival_days)
+ rec.eta_date_start = rec.date_order + timedelta(days=rec.estimated_arrival_days_start)
+ else:
+ rec.eta_date = False
+ rec.eta_date_start = False
+
+
+ def get_days_until_next_business_day(self,start_date=None, *args, **kwargs):
+ today = start_date or datetime.today().date()
+ offset = 0 # Counter jumlah hari yang ditambahkan
+ holiday = self.env['hr.public.holiday']
+
+ while True :
+ today += timedelta(days=1)
+ offset += 1
+
+ if today.weekday() >= 5:
+ continue
- for line in self.order_line:
- leadtime = line.vendor_id.leadtime
- max_leadtime = max(max_leadtime, leadtime)
+ is_holiday = holiday.search([("start_date", "=", today)])
+ if is_holiday:
+ continue
+
+ break
+
+ return offset
+
+ # def calculate_sla_by_vendor(self, products):
+ # slatime = 15
+ # for line in products:
+ # product_sla = self.env['product.sla'].search([('product_variant_id', '=', line.product_id.id)], limit=1)
+ # slatime = int(product_sla.sla) if product_sla and product_sla.sla and product_sla.sla != 'Indent' and "hari" in product_sla.sla.lower() else 15
+
+ # return {
+ # 'slatime' : slatime
+ # }
+
+ def calculate_sla_by_vendor(self, products):
+ product_ids = products.mapped('product_id.id') # Kumpulkan semua ID produk
+ include_instant = True # Default True, tetapi bisa menjadi False
+
+ # Cek apakah SEMUA produk memiliki qty_free_bandengan >= qty_needed
+ all_fast_products = all(product.product_id.qty_free_bandengan >= product.product_uom_qty for product in products)
+ if all_fast_products:
+ return {'slatime': 1, 'include_instant': include_instant}
+
+
+ # Cari semua vendor pemenang untuk produk yang diberikan
+ vendors = self.env['purchase.pricelist'].search([
+ ('product_id', 'in', product_ids),
+ ('is_winner', '=', True)
+ ])
+
+ max_slatime = 1
+
+ for vendor in vendors:
+ vendor_sla = self.env['vendor.sla'].search([('id_vendor', '=', vendor.vendor_id.id)], limit=1)
+ slatime = 15
+ if vendor_sla:
+ if vendor_sla.unit == 'hari':
+ vendor_duration = vendor_sla.duration * 24 * 60
+ include_instant = False
+ else :
+ vendor_duration = vendor_sla.duration * 60
+ include_instant = True
+
+ estimation_sla = (1 * 24 * 60) + vendor_duration
+ estimation_sla_days = estimation_sla / (24 * 60)
+ slatime = math.ceil(estimation_sla_days)
+
+ max_slatime = max(max_slatime, slatime)
+ return {'slatime': max_slatime, 'include_instant': include_instant}
+
+ @api.depends("order_line.product_id")
+ def _compute_etrts_date(self): #Function to calculate Estimated Ready To Ship Date
for rec in self:
- if rec.date_order and rec.state not in ['cancel', 'draft']:
- eta_date = datetime.now() + timedelta(days=max_leadtime)
- rec.eta_date = eta_date
- else:
- rec.eta_date = False
+ max_slatime = 1 # Default SLA jika tidak ada
+ slatime = self.calculate_sla_by_vendor(rec.order_line)
+ max_slatime = max(max_slatime, slatime['slatime'])
+
+ if rec.date_order:
+ sum_days = max_slatime + self.get_days_until_next_business_day(rec.date_order) - 1
+ if not rec.estimated_arrival_days:
+ rec.estimated_arrival_days = sum_days
+
+ eta_date = rec.date_order + timedelta(days=sum_days)
+ rec.estimated_ready_ship_date = eta_date
+ rec.commitment_date = eta_date
+ # Jika expected_ready_to_ship kosong, set nilai default
+ if not rec.expected_ready_to_ship:
+ rec.expected_ready_to_ship = eta_date
+
+
+
+ @api.onchange('expected_ready_to_ship') #Hangle Onchange form Expected Ready to Ship
+ def _onchange_expected_ready_ship_date(self):
+ for rec in self:
+ if rec.expected_ready_to_ship and rec.estimated_ready_ship_date:
+ # Hanya membandingkan tanggal saja, tanpa jam
+ expected_date = rec.expected_ready_to_ship.date()
+ estimated_date = rec.estimated_ready_ship_date.date()
+
+ if expected_date < estimated_date:
+ rec.expected_ready_to_ship = rec.estimated_ready_ship_date
+ rec.commitment_date = rec.estimated_ready_ship_date
+ raise ValidationError(
+ "Tanggal 'Expected Ready to Ship' tidak boleh lebih kecil dari {}. Mohon pilih tanggal minimal {}."
+ .format(estimated_date.strftime('%d-%m-%Y'), estimated_date.strftime('%d-%m-%Y'))
+ )
+
+ def _set_etrts_date(self):
+ for order in self:
+ if order.state in ('done', 'cancel', 'sale'):
+ raise UserError(_("You cannot change the Estimated Ready To Ship Date on a done, sale or cancelled order."))
+ # order.move_lines.write({'estimated_ready_ship_date': order.estimated_ready_ship_date})
def _prepare_invoice(self):
"""
@@ -642,15 +791,16 @@ class SaleOrder(models.Model):
if line.product_id.type == 'product':
line_no += 1
line.line_no = line_no
+
def write(self, vals):
res = super(SaleOrder, self).write(vals)
-
+ # self._compute_etrts_date()
if 'carrier_id' in vals:
for picking in self.picking_ids:
if picking.state == 'assigned':
picking.carrier_id = self.carrier_id
-
+
return res
def calculate_so_status(self):
@@ -1108,6 +1258,7 @@ class SaleOrder(models.Model):
order._set_sppkp_npwp_contact()
order.calculate_line_no()
order.send_notif_to_salesperson()
+ order._compute_etrts_date()
# order.order_line.get_reserved_from()
res = super(SaleOrder, self).action_confirm()
@@ -1503,13 +1654,14 @@ class SaleOrder(models.Model):
def create(self, vals):
# Ensure partner details are updated when a sale order is created
order = super(SaleOrder, self).create(vals)
+ order._compute_etrts_date()
# order._update_partner_details()
return order
def write(self, vals):
# Call the super method to handle the write operation
res = super(SaleOrder, self).write(vals)
-
+ # self._compute_etrts_date()
# Check if the update is coming from a save operation
# if any(field in vals for field in ['sppkp', 'npwp', 'email', 'customer_type']):
# self._update_partner_details()
@@ -1544,4 +1696,6 @@ class SaleOrder(models.Model):
raise UserError(
"SO tidak dapat ditambahkan produk baru karena SO sudah menjadi sale order.")
res = super(SaleOrder, self).write(vals)
+ if 'order_line' in vals:
+ self._compute_etrts_date()
return res \ No newline at end of file
diff --git a/indoteknik_custom/models/stock_inventory.py b/indoteknik_custom/models/stock_inventory.py
index 12a891de..69cca5bc 100644
--- a/indoteknik_custom/models/stock_inventory.py
+++ b/indoteknik_custom/models/stock_inventory.py
@@ -29,10 +29,10 @@ class StockInventory(models.Model):
"""Menentukan nomor berdasarkan kategori Adjust-In atau Adjust-Out."""
name_upper = record.name.upper() if record.name else ""
- if self.adjusment_type == 'out' or "ADJUST OUT" in name_upper or "ADJUST-OUT" in name_upper or "OUT" in name_upper:
+ if self.adjusment_type == 'out':
last_number = self._get_last_sequence("ADJUST/OUT/")
record.number = f"ADJUST/OUT/{last_number}"
- elif self.adjusment_type == 'in' or "ADJUST IN" in name_upper or "ADJUST-IN" in name_upper or "IN" in name_upper:
+ elif self.adjusment_type == 'in':
last_number = self._get_last_sequence("ADJUST/IN/")
record.number = f"ADJUST/IN/{last_number}"
else:
@@ -54,6 +54,26 @@ class StockInventory(models.Model):
@api.model
def create(self, vals):
+ """Pastikan nomor hanya dibuat saat penyimpanan."""
+ if 'adjusment_type' in vals and not vals.get('number'):
+ vals['number'] = False # Jangan buat number otomatis dulu
+
order = super(StockInventory, self).create(vals)
- self._assign_number(order)
+
+ if order.adjusment_type:
+ self._assign_number(order) # Generate number setelah save
+
return order
+
+ def write(self, vals):
+ """Jika adjusment_type diubah, generate ulang nomor."""
+ res = super(StockInventory, self).write(vals)
+ if 'adjusment_type' in vals:
+ for record in self:
+ self._assign_number(record)
+ return res
+
+ def copy(self, default=None):
+ """Saat duplikasi, adjusment_type dikosongkan dan number tidak ikut terduplikasi."""
+ default = dict(default or {}, adjusment_type=False, number=False)
+ return super(StockInventory, self).copy(default=default)
diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py
index 36d9f63d..ab8109c7 100644
--- a/indoteknik_custom/models/stock_picking.py
+++ b/indoteknik_custom/models/stock_picking.py
@@ -1,7 +1,7 @@
from odoo import fields, models, api, _
from odoo.exceptions import AccessError, UserError, ValidationError
from odoo.tools.float_utils import float_is_zero
-from datetime import timedelta, datetime
+from datetime import timedelta, datetime as waktu
from itertools import groupby
import pytz, requests, json, requests
from dateutil import parser
@@ -12,10 +12,19 @@ import base64
import requests
import time
import logging
+import re
+from deep_translator import GoogleTranslator
_logger = logging.getLogger(__name__)
+_biteship_url = "https://api.biteship.com/v1"
+_biteship_api_key = "biteship_live.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiaW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTc0MTE1NTU4M30.pbFCai9QJv8iWhgdosf8ScVmEeP3e5blrn33CHe7Hgo"
+
+
+
class StockPicking(models.Model):
_inherit = 'stock.picking'
+ _order = 'final_seq ASC'
+
check_product_lines = fields.One2many('check.product', 'picking_id', string='Check Product', auto_join=True)
barcode_product_lines = fields.One2many('barcode.product', 'picking_id', string='Barcode Product', auto_join=True)
is_internal_use = fields.Boolean('Internal Use', help='flag which is internal use or not')
@@ -166,6 +175,68 @@ class StockPicking(models.Model):
lalamove_image_url = fields.Char(string="Lalamove Image URL")
lalamove_image_html = fields.Html(string="Lalamove Image", compute="_compute_lalamove_image_html")
+ # Biteship Section
+ biteship_id = fields.Char(string="Biteship Respon ID")
+ biteship_tracking_id = fields.Char(string="Biteship Trackcking ID")
+ biteship_waybill_id = fields.Char(string="Biteship Waybill ID")
+ # estimated_ready_ship_date = fields.Datetime(string='ET Ready to Ship', copy=False, related='sale_id.estimated_ready_ship_date')
+ # countdown_hours = fields.Float(string='Countdown in Hours', compute='_callculate_sequance', default=False, store=False, compute_sudo=False)
+ # countdown_ready_to_ship = fields.Char(string='Countdown Ready to Ship', compute='_callculate_sequance', store=False, compute_sudo=False)
+ final_seq = fields.Float(string='Remaining Time')
+
+
+ def schduled_update_sequance(self):
+ query = "SELECT update_sequance_stock_picking();"
+ self.env.cr.execute(query)
+
+
+ # @api.depends('estimated_ready_ship_date', 'state')
+ # def _callculate_sequance(self):
+ # for record in self:
+ # try :
+ # if record.estimated_ready_ship_date and record.state not in ('cancel', 'done'):
+ # rts = record.estimated_ready_ship_date - waktu.now()
+ # rts_days = rts.days
+ # rts_hours = divmod(rts.seconds, 3600)
+
+ # estimated_by_erts = rts.total_seconds() / 3600
+
+ # record.countdown_ready_to_ship = f"{rts_days} days, {rts_hours} hours"
+ # record.countdown_hours = estimated_by_erts
+ # else:
+ # record.countdown_hours = 999999999999
+ # record.countdown_ready_to_ship = False
+ # except Exception as e :
+ # _logger.error(f"Error calculating sequance {record.id}: {str(e)}")
+ # print(str(e))
+ # return { 'error': str(e) }
+
+
+ # @api.depends('estimated_ready_ship_date', 'state')
+ # def _compute_countdown_hours(self):
+ # for record in self:
+ # if record.state in ('cancel', 'done') or not record.estimated_ready_ship_date:
+ # # Gunakan nilai yang sangat besar sebagai placeholder
+ # record.countdown_hours = 999999
+ # else:
+ # delta = record.estimated_ready_ship_date - waktu.now()
+ # record.countdown_hours = delta.total_seconds() / 3600
+
+ # @api.depends('estimated_ready_ship_date', 'state')
+ # def _compute_countdown_ready_to_ship(self):
+ # for record in self:
+ # if record.state in ('cancel', 'done'):
+ # record.countdown_ready_to_ship = False
+ # else:
+ # if record.estimated_ready_ship_date:
+ # delta = record.estimated_ready_ship_date - waktu.now()
+ # days = delta.days
+ # hours, remainder = divmod(delta.seconds, 3600)
+ # record.countdown_ready_to_ship = f"{days} days, {hours} hours"
+ # record.countdown_hours = delta.total_seconds() / 3600
+ # else:
+ # record.countdown_ready_to_ship = False
+
def _compute_lalamove_image_html(self):
for record in self:
if record.lalamove_image_url:
@@ -321,7 +392,7 @@ class StockPicking(models.Model):
picking.tracking_by = self.env.user.id
ata_at_str = data.get("ata_at")
envio_ata = self._convert_to_datetime(data.get("ata_at"))
-
+
picking.driver_arrival_date = envio_ata
if data.get("status") != 'delivered':
picking.driver_arrival_date = False
@@ -332,12 +403,13 @@ class StockPicking(models.Model):
raise UserError(f"Kesalahan tidak terduga: {str(e)}")
def action_send_to_biteship(self):
- url = "https://api.biteship.com/v1/orders"
- api_key = "biteship_test.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTcyOTQ5ODAwMX0.L6C73couP4-cgVEfhKI2g7eMCMo3YOFSRZhS-KSuHNA"
-
+
+ if self.biteship_tracking_id:
+ raise UserError(f"Order ini sudah dikirim ke Biteship. Dengan Tracking Id: {self.biteship_tracking_id}")
+
# Mencari data sale.order.line berdasarkan sale_id
products = self.env['sale.order.line'].search([('order_id', '=', self.sale_id.id)])
-
+
# Fungsi untuk membangun items_data dari order lines
def build_items_data(lines):
return [{
@@ -370,6 +442,7 @@ class StockPicking(models.Model):
})
payload = {
+ "reference_id " : self.sale_id.name,
"shipper_contact_name": self.carrier_id.pic_name or '',
"shipper_contact_phone": self.carrier_id.pic_phone or '',
"shipper_organization": self.carrier_id.name,
@@ -381,7 +454,8 @@ class StockPicking(models.Model):
"destination_contact_phone": self.real_shipping_id.phone or self.real_shipping_id.mobile,
"destination_address": self.real_shipping_id.street,
"destination_postal_code": self.real_shipping_id.zip,
- "courier_type": "reg",
+ "origin_note": "BELAKANG INDOMARET",
+ "courier_type": self.sale_id.delivery_service_type or "reg",
"courier_company": self.carrier_id.name.lower(),
"delivery_type": "now",
"destination_postal_code": self.real_shipping_id.zip,
@@ -389,27 +463,42 @@ class StockPicking(models.Model):
}
# Cek jika pengiriman instant atau same_day
- if "instant" in self.sale_id.delivery_service_type or "same_day" in self.sale_id.delivery_service_type:
+ if self.sale_id.delivery_service_type and ("instant" in self.sale_id.delivery_service_type or "same_day" in self.sale_id.delivery_service_type):
payload.update({
- "origin_note": "BELAKANG INDOMARET",
- "courier_company": self.carrier_id.name.lower(),
- "courier_type": self.sale_id.delivery_service_type,
- "delivery_type": "now",
- "items": items_data_instant # Gunakan items untuk instant
+ "origin_coordinate" :{
+ "latitude": -6.3031123,
+ "longitude" : 106.7794934999
+ },
+ "destination_coordinate" : {
+ "latitude": self.real_shipping_id.latitude,
+ "longitude": self.real_shipping_id.longtitude,
+ },
+ "items": items_data_instant
})
-
+
+ api_key = _biteship_api_key
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
# Kirim request ke Biteship
- response = requests.post(url, headers=headers, json=payload)
+ response = requests.post(_biteship_url+'/orders', headers=headers, json=payload)
- if response.status_code == 201:
- return response.json()
+ if response.status_code == 200:
+ data = response.json()
+
+ self.biteship_id = data.get("id", "")
+ self.biteship_tracking_id = data.get("courier", {}).get("tracking_id", "")
+ self.biteship_waybill_id = data.get("courier", {}).get("waybill_id", "")
+ self.delivery_tracking_no = data.get("courier", {}).get("waybill_id", "")
+
+ return data
else:
- raise UserError(f"Error saat mengirim ke Biteship: {response.content}")
+ error_data = response.json()
+ error_message = error_data.get("error", "Unknown error")
+ error_code = error_data.get("code", "No code provided")
+ raise UserError(f"Error saat mengirim ke Biteship: {error_message} (Code: {error_code})")
@api.constrains('driver_departure_date')
def constrains_driver_departure_date(self):
@@ -845,6 +934,7 @@ class StockPicking(models.Model):
self.calculate_line_no()
self.date_done = datetime.datetime.utcnow()
self.state_reserve = 'done'
+ self.final_seq = 0
self.send_mail_bills()
return res
@@ -1039,11 +1129,14 @@ class StockPicking(models.Model):
def get_tracking_detail(self):
self.ensure_one()
+
+ order = self.env['sale.order'].search([('name', '=', self.sale_id.name)], limit=1)
response = {
'delivery_order': {
'name': self.name,
'carrier': self.carrier_id.name or '',
+ 'service' : order.delivery_service_type or '',
'receiver_name': '',
'receiver_city': ''
},
@@ -1052,8 +1145,21 @@ class StockPicking(models.Model):
'waybill_number': self.delivery_tracking_no or '',
'delivery_status': None,
'eta': self.generate_eta_delivery(),
+ 'is_biteship': True if self.biteship_id else False,
'manifests': self.get_manifests()
}
+
+ if self.biteship_id :
+ histori = self.get_manifest_biteship()
+ eta_start = order.date_order + timedelta(days=order.estimated_arrival_days_start)
+ eta_end = order.date_order + timedelta(days=order.estimated_arrival_days)
+ formatted_eta = f"{eta_start.strftime('%d %b')} - {eta_end.strftime('%d %b %Y')}"
+ response['eta'] = formatted_eta
+ response['manifests'] = histori.get("manifests", [])
+ response['delivered'] = histori.get("delivered", False) or self.sj_return_date != False or self.driver_arrival_date != False
+ response['status'] = self._map_status_biteship(histori.get("delivered"))
+
+ return response
if not self.waybill_id or len(self.waybill_id.manifest_ids) == 0:
response['delivered'] = self.sj_return_date != False or self.driver_arrival_date != False
@@ -1066,6 +1172,77 @@ class StockPicking(models.Model):
return response
+ def get_manifest_biteship(self):
+ api_key = _biteship_api_key
+ headers = {
+ "Authorization": f"Bearer {api_key}",
+ "Content-Type": "application/json"
+ }
+
+
+ manifests = []
+
+ try:
+ # Kirim request ke Biteship
+ response = requests.get(_biteship_url+'/trackings/'+self.biteship_tracking_id, headers=headers, json=manifests)
+ result = response.json()
+ description = {
+ 'confirmed' : 'Indoteknik telah melakukan permintaan pick-up',
+ 'allocated' : 'Kurir akan melakukan pick-up pesanan',
+ 'picking_up' : 'Kurir sedang dalam perjalanan menuju lokasi pick-up',
+ 'picked' : 'Pesanan sudah di pick-up kurir '+result.get("courier", {}).get("name", ""),
+ 'on_hold' : 'Pesanan ditahan sementara karena masalah pengiriman',
+ 'dropping_off' : 'Kurir sudah ditugaskan dan pesanan akan segera diantar ke pembeli',
+ 'delivered' : 'Pesanan telah sampai dan diterima oleh '+result.get("destination", {}).get("contact_name", "")
+ }
+ if(result.get('success') == True):
+ history = result.get("history", [])
+ status = result.get("status", "")
+
+ for entry in reversed(history):
+ manifests.append({
+ "status": re.sub(r'[^a-zA-Z0-9\s]', ' ', entry["status"]).lower().capitalize(),
+ "datetime": self._convert_to_local_time(entry["updated_at"]),
+ # "description": GoogleTranslator(source='auto', target='id').translate(entry["note"]),
+ "description": description[entry["status"]],
+ })
+
+ return {
+ "manifests": manifests,
+ "delivered": status
+ }
+
+ return manifests
+ except Exception as e :
+ _logger.error(f"Error fetching Biteship order for picking {self.id}: {str(e)}")
+ return { 'error': str(e) }
+
+ def _convert_to_local_time(self, iso_date):
+ try:
+ dt_with_tz = waktu.fromisoformat(iso_date)
+ utc_dt = dt_with_tz.astimezone(pytz.utc)
+
+ local_tz = pytz.timezone("Asia/Jakarta")
+ local_dt = utc_dt.astimezone(local_tz)
+
+ return local_dt.strftime("%Y-%m-%d %H:%M:%S")
+ except Exception as e:
+ return str(e)
+
+ def _map_status_biteship(self, status):
+ status_mapping = {
+ "confirmed": "pending",
+ "scheduled": "pending",
+ "allocated": "pending",
+ "picking_up": "pending",
+ "picked": "shipment",
+ "cancelled": "cancelled",
+ "on_hold": "on_hold",
+ "dropping_off": "shipment",
+ "delivered": "completed"
+ }
+ return status_mapping.get(status, "Hubungi Admin")
+
def generate_eta_delivery(self):
current_date = datetime.datetime.now()
prepare_days = 3
diff --git a/indoteknik_custom/models/user_pengajuan_tempo.py b/indoteknik_custom/models/user_pengajuan_tempo.py
index 0fdcdbeb..0b3ab63d 100644
--- a/indoteknik_custom/models/user_pengajuan_tempo.py
+++ b/indoteknik_custom/models/user_pengajuan_tempo.py
@@ -74,6 +74,7 @@ class UserPengajuanTempo(models.Model):
# Pengiriman
pic_tittle = fields.Char(string='Tittle PIC Penerimaan Barang')
+ pic_mobile = fields.Char(string='Nomor HP PIC Penerimaan Barang')
pic_name = fields.Char(string='Nama PIC Penerimaan Barang')
street_pengiriman = fields.Char(string="Alamat Perusahaan")
state_id_pengiriman = fields.Many2one('res.country.state', string='State')
@@ -83,6 +84,7 @@ class UserPengajuanTempo(models.Model):
zip_pengiriman = fields.Char(string="Zip")
invoice_pic_tittle = fields.Char(string='Tittle PIC Penerimaan Invoice')
invoice_pic = fields.Char(string='Nama PIC Penerimaan Invoice')
+ invoice_pic_mobile = fields.Char(string='Nomor HP PIC Penerimaan Invoice')
street_invoice = fields.Char(string="Alamat Perusahaan")
state_id_invoice = fields.Many2one('res.country.state', string='State')
city_id_invoice = fields.Many2one('vit.kota', string='City')
@@ -97,6 +99,7 @@ class UserPengajuanTempo(models.Model):
dokumen_invoice = fields.Char(string='Dokumen yang dilampirkan saat Pengiriman Invoice')
is_same_address = fields.Boolean(string="Same Address pengiriman invoicr dan alamat pengiriman barang")
is_same_address_street = fields.Boolean(string="Same Address pengiriman barang dan alamat bisnis")
+ dokumen_prosedur = fields.Many2many('ir.attachment', 'dokumen_prosedur_rel', string="Dokumen Prosedur", tracking=True)
# Referensi
supplier_ids = fields.Many2many('user.pengajuan.tempo.line', string="Suppliers")
diff --git a/indoteknik_custom/models/user_pengajuan_tempo_request.py b/indoteknik_custom/models/user_pengajuan_tempo_request.py
index be4293a0..abcb6f2f 100644
--- a/indoteknik_custom/models/user_pengajuan_tempo_request.py
+++ b/indoteknik_custom/models/user_pengajuan_tempo_request.py
@@ -108,6 +108,7 @@ class UserPengajuanTempoRequest(models.Model):
# Pengiriman
pic_tittle = fields.Char(string='Tittle PIC Penerimaan Barang', related='pengajuan_tempo_id.pic_tittle', store=True, readonly=False)
+ pic_mobile = fields.Char(string='Nomor HP PIC Penerimaan Barang', related='pengajuan_tempo_id.pic_mobile', store=True, readonly=False)
pic_name = fields.Char(string='Nama PIC Penerimaan Barang', related='pengajuan_tempo_id.pic_name', store=True, readonly=False)
street_pengiriman = fields.Char(string="Alamat Perusahaan", related='pengajuan_tempo_id.street_pengiriman', store=True, readonly=False)
state_id_pengiriman = fields.Many2one('res.country.state', string='State', related='pengajuan_tempo_id.state_id_pengiriman', store=True, readonly=False)
@@ -116,6 +117,7 @@ class UserPengajuanTempoRequest(models.Model):
subDistrict_id_pengiriman = fields.Many2one('vit.kelurahan', string='Kelurahan', related='pengajuan_tempo_id.subDistrict_id_pengiriman', store=True, readonly=False)
zip_pengiriman = fields.Char(string="Zip", related='pengajuan_tempo_id.zip_pengiriman', store=True, readonly=False)
invoice_pic_tittle = fields.Char(string='Tittle PIC Penerimaan Invoice', related='pengajuan_tempo_id.invoice_pic_tittle', store=True, readonly=False)
+ invoice_pic_mobile = fields.Char(string='Nomor HP PIC Penerimaan Invoice', related='pengajuan_tempo_id.invoice_pic_mobile', store=True, readonly=False)
invoice_pic = fields.Char(string='Nama PIC Penerimaan Invoice', related='pengajuan_tempo_id.invoice_pic', store=True, readonly=False)
street_invoice = fields.Char(string="Alamat Perusahaan", related='pengajuan_tempo_id.street_invoice', store=True, readonly=False)
state_id_invoice = fields.Many2one('res.country.state', string='State', related='pengajuan_tempo_id.state_id_invoice', store=True, readonly=False)
@@ -130,7 +132,14 @@ class UserPengajuanTempoRequest(models.Model):
dokumen_invoice = fields.Char(string='Dokumen yang dilampirkan saat Pengiriman Invoice', related='pengajuan_tempo_id.dokumen_invoice', store=True, readonly=False)
is_same_address = fields.Boolean(string="Same Address pengiriman invoicr dan alamat pengiriman barang", related='pengajuan_tempo_id.is_same_address', store=True, readonly=False)
is_same_address_street = fields.Boolean(string="Same Address pengiriman barang dan alamat bisnis", related='pengajuan_tempo_id.is_same_address_street', store=True, readonly=False)
-
+ dokumen_prosedur = fields.Many2many(
+ 'ir.attachment',
+ 'dokumen_prosedur_rel',
+ string="Dokumen Prosedur",
+ related='pengajuan_tempo_id.dokumen_prosedur',
+ readonly=False,
+ tracking=3
+ )
#Referensi
supplier_ids = fields.Many2many('user.pengajuan.tempo.line',related='pengajuan_tempo_id.supplier_ids', string="Suppliers", readonly=False)
@@ -292,16 +301,17 @@ class UserPengajuanTempoRequest(models.Model):
self.pengajuan_tempo_id.finance_mobile = self.finance_mobile
self.pengajuan_tempo_id.finance_email = self.finance_email
- @api.onchange('pic_tittle', 'pic_name', 'street_pengiriman', 'state_id_pengiriman', 'city_id_pengiriman',
+ @api.onchange('pic_tittle','pic_mobile', 'pic_name', 'street_pengiriman', 'state_id_pengiriman', 'city_id_pengiriman',
'zip_pengiriman', 'district_id_pengiriman', 'subDistrict_id_pengiriman'
- 'invoice_pic_tittle', 'invoice_pic', 'street_invoice', 'state_id_invoice', 'city_id_invoice',
+ 'invoice_pic_tittle','invoice_pic_mobile', 'invoice_pic', 'street_invoice', 'state_id_invoice', 'city_id_invoice',
'district_id_invoice', 'subDistrict_id_invoice', 'zip_invoice',
'tukar_invoice', 'jadwal_bayar', 'dokumen_pengiriman', 'dokumen_pengiriman_input', 'dokumen_invoice',
- 'is_same_address', 'is_same_address_street')
+ 'is_same_address', 'is_same_address_street','dokumen_prosedur')
def _onchange_related_fields_pengiriman(self):
if self.pengajuan_tempo_id:
# Perbarui nilai di pengajuan_tempo_id
self.pengajuan_tempo_id.pic_tittle = self.pic_tittle
+ self.pengajuan_tempo_id.pic_mobile = self.pic_mobile
self.pengajuan_tempo_id.pic_name = self.pic_name
self.pengajuan_tempo_id.street_pengiriman = self.street_pengiriman
self.pengajuan_tempo_id.state_id_pengiriman = self.state_id_pengiriman
@@ -310,6 +320,7 @@ class UserPengajuanTempoRequest(models.Model):
self.pengajuan_tempo_id.subDistrict_id_pengiriman = self.subDistrict_id_pengiriman
self.pengajuan_tempo_id.zip_pengiriman = self.zip_pengiriman
self.pengajuan_tempo_id.invoice_pic_tittle = self.invoice_pic_tittle
+ self.pengajuan_tempo_id.invoice_pic_mobile = self.invoice_pic_mobile
self.pengajuan_tempo_id.invoice_pic = self.invoice_pic
self.pengajuan_tempo_id.street_invoice = self.street_invoice
self.pengajuan_tempo_id.state_id_invoice = self.state_id_invoice
@@ -324,6 +335,7 @@ class UserPengajuanTempoRequest(models.Model):
self.pengajuan_tempo_id.dokumen_invoice = self.dokumen_invoice
self.pengajuan_tempo_id.is_same_address = self.is_same_address
self.pengajuan_tempo_id.is_same_address_street = self.is_same_address_street
+ self.pengajuan_tempo_id.dokumen_prosedur = self.dokumen_prosedur
@api.onchange('supplier_ids')
def _onchange_supplier_ids(self):
@@ -337,7 +349,6 @@ class UserPengajuanTempoRequest(models.Model):
def _onchange_related_fields_dokumen(self):
if self.pengajuan_tempo_id:
# Perbarui nilai di pengajuan_tempo_id
- self.pengajuan_tempo_id.dokumen_nib = self.dokumen_nib
self.pengajuan_tempo_id.dokumen_siup = self.dokumen_siup
self.pengajuan_tempo_id.dokumen_tdp = self.dokumen_tdp
self.pengajuan_tempo_id.dokumen_skdp = self.dokumen_skdp
@@ -509,6 +520,7 @@ class UserPengajuanTempoRequest(models.Model):
{
"type": "delivery",
"name": self.pengajuan_tempo_id.pic_name,
+ "phone": self.pengajuan_tempo_id.pic_mobile,
"street": self.pengajuan_tempo_id.street_pengiriman,
"state_id": self.pengajuan_tempo_id.state_id_pengiriman.id,
"kota_id": self.pengajuan_tempo_id.city_id_pengiriman.id,
@@ -519,6 +531,7 @@ class UserPengajuanTempoRequest(models.Model):
{
"type": "invoice",
"name": self.pengajuan_tempo_id.invoice_pic,
+ "phone": self.pengajuan_tempo_id.invoice_pic_mobile,
"street": self.pengajuan_tempo_id.street_invoice,
"state_id": self.pengajuan_tempo_id.state_id_invoice.id,
"kota_id": self.pengajuan_tempo_id.city_id_invoice.id,
@@ -583,6 +596,10 @@ class UserPengajuanTempoRequest(models.Model):
self.user_company_id.dokumen_pengiriman = self.pengajuan_tempo_id.dokumen_pengiriman
self.user_company_id.dokumen_pengiriman_input = self.pengajuan_tempo_id.dokumen_pengiriman_input
self.user_company_id.dokumen_invoice = self.pengajuan_tempo_id.dokumen_invoice
+ self.user_company_id.dokumen_prosedur = self.pengajuan_tempo_id.dokumen_prosedur[0] if self.pengajuan_tempo_id.dokumen_prosedur else []
+ if self.user_company_id.dokumen_prosedur:
+ self.user_company_id.message_post(body='Dokumen Prosedur',
+ attachment_ids=[self.user_company_id.dokumen_prosedur.id])
# Referensi
self.user_company_id.supplier_ids = self.pengajuan_tempo_id.supplier_ids
@@ -592,6 +609,8 @@ class UserPengajuanTempoRequest(models.Model):
if self.user_company_id.dokumen_npwp:
self.user_company_id.message_post(body='Dokumen NPWP', attachment_ids=[self.user_company_id.dokumen_npwp.id])
+
+
self.user_company_id.dokumen_sppkp = self.pengajuan_tempo_id.dokumen_sppkp[0] if self.pengajuan_tempo_id.dokumen_sppkp else []
if self.user_company_id.dokumen_sppkp:
self.user_company_id.message_post(body='Dokumen SPPKP', attachment_ids=[self.user_company_id.dokumen_sppkp.id])
diff --git a/indoteknik_custom/models/vendor_sla.py b/indoteknik_custom/models/vendor_sla.py
new file mode 100644
index 00000000..b052e6cb
--- /dev/null
+++ b/indoteknik_custom/models/vendor_sla.py
@@ -0,0 +1,102 @@
+from odoo import models, fields, api
+import logging
+import math
+_logger = logging.getLogger(__name__)
+
+class VendorSLA(models.Model):
+ _name = 'vendor.sla'
+ _description = 'Vendor SLA'
+ _rec_name = 'id_vendor'
+
+ id_vendor = fields.Many2one('res.partner', string='Name', domain="[('industry_id', '=', 46)]")
+ duration = fields.Integer(string='Duration', description='SLA Duration')
+ unit = fields.Selection(
+ [('jam', 'Jam'),('hari', 'Hari')],
+ string="SLA Time"
+ )
+ duration_unit = fields.Char(string="Duration (Unit)", compute="_compute_duration_unit")
+
+ # pertama, lakukan group by vendor pada modul purchase.order
+ # kedua, pada setiap Purchase order pada group by vendor tersebut, lakukan penghitungan penjumlahan setiap nilai datetime field date_planed dikurangi date_approve purchase order
+ # dibagi jumlah data dari setiap Purchase order pada group by vendor tersebut. hasilnya lalu di gunakan untuk mengset nilai duration
+ def generate_vendor_id_sla(self):
+ # Step 1: Group stock pickings by vendor based on operation type
+ stock_picking_env = self.env['stock.picking']
+ stock_moves = stock_picking_env.read_group(
+ domain=[
+ ('state', '=', 'done'),
+ ('location_id', '=', 4), # Partner Locations/Vendors
+ ('location_dest_id', '=', 57) # BU/Stock
+ ],
+ fields=['partner_id', 'date_done', 'scheduled_date'],
+ groupby=['partner_id'],
+ lazy=False
+ )
+
+ for group in stock_moves:
+ partner_id = group['partner_id'][0]
+ total_duration = 0
+ count = 0
+
+ # Step 2: Calculate the average duration for each vendor
+ pos_for_vendor = stock_picking_env.search([
+ ('partner_id', '=', partner_id),
+ ('state', '=', 'done'),
+ ])
+
+ for po in pos_for_vendor:
+ if po.date_done and po.purchase_id.date_approve:
+ date_of_transfer = fields.Datetime.to_datetime(po.date_done)
+ po_confirmation_date = fields.Datetime.to_datetime(po.purchase_id.date_approve)
+ if date_of_transfer < po_confirmation_date: continue
+
+ days_difference = (date_of_transfer - po_confirmation_date).days
+ if days_difference > 14:
+ continue
+ duration = (date_of_transfer - po_confirmation_date).total_seconds() / 3600 # Convert to hours
+ total_duration += duration
+ count += 1
+
+ if count > 0:
+ average_duration = total_duration / count
+
+ # Step 3: Update the duration field in the corresponding res.partner record
+ vendor_sla = self.search([('id_vendor', '=', partner_id)], limit=1)
+
+ # Konversi jam ke hari jika diperlukan
+ if average_duration >= 24:
+ days = average_duration / 24
+ if days - int(days) > 0.5: # Jika sisa lebih dari 0,5, bulatkan ke atas
+ days = int(days) + 1
+ else: # Jika sisa 0,5 atau kurang, bulatkan ke bawah
+ days = int(days)
+ duration_to_save = days
+ unit_to_save = 'hari'
+ else:
+ duration_to_save = round(average_duration)
+ unit_to_save = 'jam'
+
+ # Update atau create vendor SLA record
+ if vendor_sla:
+ vendor_sla.write({
+ 'duration': duration_to_save,
+ 'unit': unit_to_save
+ })
+ else:
+ self.create({
+ 'id_vendor': partner_id,
+ 'duration': duration_to_save,
+ 'unit': unit_to_save
+ })
+ _logger.info(f'Proses SLA untuk Vendor selesai dilakukan')
+
+ @api.depends('duration', 'unit')
+ def _compute_duration_unit(self):
+ for record in self:
+ if record.duration and record.unit:
+ record.duration_unit = f"{record.duration} {record.unit}"
+ else:
+ record.duration_unit = "-"
+
+
+ \ No newline at end of file
diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv
index 5e0c2221..4d0e51eb 100755
--- a/indoteknik_custom/security/ir.model.access.csv
+++ b/indoteknik_custom/security/ir.model.access.csv
@@ -151,18 +151,20 @@ access_sales_order_fulfillment_v2,access.sales.order.fulfillment.v2,model_sales_
access_v_move_outstanding,access.v.move.outstanding,model_v_move_outstanding,,1,1,1,1
access_va_multi_approve,access.va.multi.approve,model_va_multi_approve,,1,1,1,1
access_va_multi_reject,access.va.multi.reject,model_va_multi_reject,,1,1,1,1
+access_vendor_sla,access.vendor_sla,model_vendor_sla,,1,1,1,1
access_check_product,access.check.product,model_check_product,,1,1,1,1
access_stock_immediate_transfer,access.stock.immediate.transfer,model_stock_immediate_transfer,,1,1,1,1
access_coretax_faktur,access.coretax.faktur,model_coretax_faktur,,1,1,1,1
access_purchase_order_unlock_wizard,access.purchase.order.unlock.wizard,model_purchase_order_unlock_wizard,,1,1,1,1
-
access_User_pengajuan_tempo_line,access.user.pengajuan.tempo.line,model_user_pengajuan_tempo_line,,1,1,1,1
access_user_pengajuan_tempo,access.user.pengajuan.tempo,model_user_pengajuan_tempo,,1,1,1,1
access_reject_reason_wizard,reject.reason.wizard,model_reject_reason_wizard,,1,1,1,0
access_confirm_approval_wizard,confirm.approval.wizard,model_confirm_approval_wizard,,1,1,1,0
+access_hr_public_holiday,confirm.hr.public.holiday,model_hr_public_holiday,,1,1,1,0
access_barcode_product,access.barcode.product,model_barcode_product,,1,1,1,1
access_barcoding_product,access.barcoding.product,model_barcoding_product,,1,1,1,1
access_barcoding_product_line,access.barcoding.product.line,model_barcoding_product_line,,1,1,1,1
access_account_payment_register,access.account.payment.register,model_account_payment_register,,1,1,1,1
access_stock_inventory,access.stock.inventory,model_stock_inventory,,1,1,1,1
access_cancel_reason_order,cancel.reason.order,model_cancel_reason_order,,1,1,1,0
+access_shipping_option,shipping.option,model_shipping_option,,1,1,1,1
diff --git a/indoteknik_custom/views/customer_commision.xml b/indoteknik_custom/views/customer_commision.xml
index 51172b1c..bb1628bc 100644
--- a/indoteknik_custom/views/customer_commision.xml
+++ b/indoteknik_custom/views/customer_commision.xml
@@ -17,6 +17,8 @@
decoration-danger="payment_status == 'pending'"
widget="badge"/>
<field name="brand_ids" widget="many2many_tags"/>
+ <field name="grouped_so_number" readonly="1"/>
+ <field name="grouped_invoice_number" readonly="1"/>
</tree>
</field>
</record>
@@ -62,6 +64,8 @@
<field name="description"/>
<field name="commision_percent"/>
<field name="commision_amt"/>
+ <field name="grouped_so_number" readonly="1"/>
+ <field name="grouped_invoice_number" readonly="1"/>
</group>
<group>
<div>
diff --git a/indoteknik_custom/views/product_product.xml b/indoteknik_custom/views/product_product.xml
index 71748e44..b214dc87 100644
--- a/indoteknik_custom/views/product_product.xml
+++ b/indoteknik_custom/views/product_product.xml
@@ -31,6 +31,14 @@
<field name="code">model.action_sync_to_solr()</field>
</record>
+ <record id="ir_actions_server_product_sla_generate" model="ir.actions.server">
+ <field name="name">Generate Product SLA</field>
+ <field name="model_id" ref="product.model_product_product"/>
+ <field name="binding_model_id" ref="product.model_product_product"/>
+ <field name="state">code</field>
+ <field name="code">model.generate_product_sla()</field>
+ </record>
+
<data noupdate="1">
<record id="cron_variant_solr_flag_solr" model="ir.cron">
<field name="name">Sync Variant To Solr: Solr Flag 2</field>
diff --git a/indoteknik_custom/views/product_sla.xml b/indoteknik_custom/views/product_sla.xml
index 8b0e874b..9179730f 100644
--- a/indoteknik_custom/views/product_sla.xml
+++ b/indoteknik_custom/views/product_sla.xml
@@ -6,7 +6,9 @@
<field name="arch" type="xml">
<tree create="false">
<field name="product_variant_id"/>
- <field name="avg_leadtime"/>
+ <field name="sla_vendor_id" string="Name Vendor"/>
+ <field name="sla_vendor_duration" string="SLA Vendor"/>
+ <field name="sla_logistic_duration_unit" string="SLA Logistic"/>
<field name="sla"/>
</tree>
</field>
@@ -21,7 +23,8 @@
<group>
<group>
<field name="product_variant_id"/>
- <field name="avg_leadtime"/>
+ <field name="sla_logistic"/>
+ <field name="sla_logistic_unit"/>
<field name="sla"/>
<field name="version"/>
</group>
diff --git a/indoteknik_custom/views/public_holiday.xml b/indoteknik_custom/views/public_holiday.xml
new file mode 100644
index 00000000..146c5b0b
--- /dev/null
+++ b/indoteknik_custom/views/public_holiday.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<odoo>
+ <data>
+ <!-- Security Access Rights -->
+ <record id="model_hr_public_holiday_access" model="ir.model.access">
+ <field name="name">hr.public.holiday access</field>
+ <field name="model_id" ref="model_hr_public_holiday"/>
+ <field name="group_id" eval="False"/>
+ <field name="perm_read" eval="True"/>
+ <field name="perm_write" eval="True"/>
+ <field name="perm_create" eval="True"/>
+ <field name="perm_unlink" eval="True"/>
+ </record>
+
+ <!-- Public Holiday Form View -->
+ <record id="view_hr_public_holiday_form" model="ir.ui.view">
+ <field name="name">hr.public.holiday.form</field>
+ <field name="model">hr.public.holiday</field>
+ <field name="arch" type="xml">
+ <form string="Public Holiday">
+ <sheet>
+ <group>
+ <field name="name"/>
+ <field name="start_date"/>
+ </group>
+ </sheet>
+ </form>
+ </field>
+ </record>
+
+ <record id="view_hr_public_holiday_tree" model="ir.ui.view">
+ <field name="name">hr.public.holiday.tree</field>
+ <field name="model">hr.public.holiday</field>
+ <field name="arch" type="xml">
+ <tree string="Public Holidays">
+ <field name="name"/>
+ <field name="start_date"/>
+ </tree>
+ </field>
+ </record>
+ <record id="action_hr_public_holiday" model="ir.actions.act_window">
+ <field name="name">Public Holidays</field>
+ <field name="res_model">hr.public.holiday</field>
+ <field name="view_mode">tree,form</field>
+ </record>
+
+ <menuitem
+ id="hr_public_holiday"
+ name="Public Holiday"
+ parent="website_sale.menu_orders"
+ sequence="1"
+ action="action_hr_public_holiday"
+ />
+ </data>
+</odoo>
diff --git a/indoteknik_custom/views/res_partner.xml b/indoteknik_custom/views/res_partner.xml
index af5e0db3..cb9fa3ac 100644
--- a/indoteknik_custom/views/res_partner.xml
+++ b/indoteknik_custom/views/res_partner.xml
@@ -131,6 +131,7 @@
<group string="Pengiriman" colspan="4">
<group>
<field name="pic_name"/>
+ <field name="pic_mobile"/>
<field name="street_pengiriman"/>
<field name="state_id_pengiriman"/>
<field name="city_id_pengiriman"/>
@@ -140,6 +141,7 @@
</group>
<group>
<field name="invoice_pic"/>
+ <field name="invoice_pic_mobile"/>
<field name="street_invoice"/>
<field name="state_id_invoice"/>
<field name="city_id_invoice"/>
@@ -150,6 +152,7 @@
<group>
<field name="tukar_invoice"/>
<field name="jadwal_bayar"/>
+ <field name="dokumen_prosedur" />
<field name="dokumen_pengiriman"/>
<field name="dokumen_pengiriman_input"/>
<field name="dokumen_invoice"/>
diff --git a/indoteknik_custom/views/sale_order.xml b/indoteknik_custom/views/sale_order.xml
index 163330c5..0d190f37 100755
--- a/indoteknik_custom/views/sale_order.xml
+++ b/indoteknik_custom/views/sale_order.xml
@@ -68,7 +68,10 @@
<field name="compute_fullfillment" invisible="1"/>
</field>
<field name="tag_ids" position="after">
+ <field name="eta_date_start"/>
+ <t t-esc="' to '"/>
<field name="eta_date" readonly="1"/>
+ <field name="expected_ready_to_ship" />
<field name="flash_sale"/>
<field name="margin_after_delivery_purchase"/>
<field name="percent_margin_after_delivery_purchase"/>
@@ -97,6 +100,7 @@
<field name="sales_tax_id" domain="[('type_tax_use','=','sale'), ('active', '=', True)]" required="1"/>
<field name="carrier_id" required="1"/>
<field name="delivery_service_type" readonly="1"/>
+ <field name="shipping_option_id"/>
</field>
<field name="medium_id" position="after">
<field name="date_doc_kirim" readonly="1"/>
diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml
index 50ea40bf..dadd5021 100644
--- a/indoteknik_custom/views/stock_picking.xml
+++ b/indoteknik_custom/views/stock_picking.xml
@@ -7,7 +7,8 @@
<field name="inherit_id" ref="stock.vpicktree"/>
<field name="arch" type="xml">
<tree position="attributes">
- <attribute name="default_order">create_date desc</attribute>
+ <attribute name="default_order">final_seq asc</attribute>
+ <!-- <attribute name="default_order">create_date desc</attribute> -->
</tree>
<field name="json_popover" position="after">
<field name="date_done" optional="hide"/>
@@ -18,6 +19,9 @@
<field name="note" optional="hide"/>
<field name="date_reserved" optional="hide"/>
<field name="state_reserve" optional="hide"/>
+ <field name="final_seq"/>
+ <!-- <field name="countdown_hours" optional="hide"/>
+ <field name="countdown_ready_to_ship" /> -->
</field>
<field name="partner_id" position="after">
<field name="purchase_representative_id"/>
diff --git a/indoteknik_custom/views/user_pengajuan_tempo.xml b/indoteknik_custom/views/user_pengajuan_tempo.xml
index 7f1faa41..4eebe9e4 100644
--- a/indoteknik_custom/views/user_pengajuan_tempo.xml
+++ b/indoteknik_custom/views/user_pengajuan_tempo.xml
@@ -53,6 +53,7 @@
<group string="Pengiriman" colspan="4">
<group>
<field name="pic_name"/>
+ <field name="pic_mobile"/>
<field name="street_pengiriman"/>
<field name="state_id_pengiriman"/>
<field name="city_id_pengiriman"/>
@@ -62,6 +63,7 @@
</group>
<group>
<field name="invoice_pic"/>
+ <field name="invoice_pic_mobile"/>
<field name="street_invoice"/>
<field name="state_id_invoice"/>
<field name="city_id_invoice"/>
diff --git a/indoteknik_custom/views/user_pengajuan_tempo_request.xml b/indoteknik_custom/views/user_pengajuan_tempo_request.xml
index 7063231b..339ce8db 100644
--- a/indoteknik_custom/views/user_pengajuan_tempo_request.xml
+++ b/indoteknik_custom/views/user_pengajuan_tempo_request.xml
@@ -102,6 +102,7 @@
<Page string="Pengiriman">
<group>
<field name="pic_name"/>
+ <field name="pic_mobile"/>
<field name="street_pengiriman"/>
<field name="state_id_pengiriman"/>
<field name="city_id_pengiriman"/>
@@ -111,6 +112,7 @@
</group>
<group>
<field name="invoice_pic"/>
+ <field name="invoice_pic_mobile"/>
<field name="street_invoice"/>
<field name="state_id_invoice"/>
<field name="city_id_invoice"/>
@@ -121,6 +123,7 @@
<group>
<field name="tukar_invoice"/>
<field name="jadwal_bayar"/>
+ <field name="dokumen_prosedur" widget="many2many_binary"/>
<field name="dokumen_pengiriman"/>
<field name="dokumen_pengiriman_input"/>
<field name="dokumen_invoice"/>
diff --git a/indoteknik_custom/views/vendor_sla.xml b/indoteknik_custom/views/vendor_sla.xml
new file mode 100644
index 00000000..cf4425eb
--- /dev/null
+++ b/indoteknik_custom/views/vendor_sla.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<odoo>
+ <record id="vendor_action" model="ir.actions.act_window">
+ <field name="name">Vendor SLA</field>
+ <field name="res_model">vendor.sla</field>
+ <field name="view_mode">tree,form</field>
+ </record>
+
+ <record id="vendor_tree" model="ir.ui.view">
+ <field name="name">Vendor SLA</field>
+ <field name="model">vendor.sla</field>
+ <field name="arch" type="xml">
+ <tree>
+ <field name="id_vendor" string="Vendor Name" />
+ <field name="duration_unit" string="Duration" />
+ </tree>
+ </field>
+ </record>
+
+ <record id="vendor_sla_view" model="ir.ui.view">
+ <field name="name">Vendor SLA</field>
+ <field name="model">vendor.sla</field>
+ <field name="arch" type="xml">
+ <form>
+ <sheet>
+ <group>
+ <field name="id_vendor" string="Vendor Name" />
+ <field name="duration" string="SLA Duration" />
+ <field name="unit" string="SLA Time" />
+ </group>
+ </sheet>
+ </form>
+ </field>
+ </record>
+
+ <menuitem id="menu_vendor_sla"
+ name="Vendor SLA"
+ parent="menu_monitoring_in_purchase"
+ sequence="1"
+ action="vendor_action"
+ />
+</odoo> \ No newline at end of file
diff --git a/indoteknik_custom/views/x_banner_category.xml b/indoteknik_custom/views/x_banner_category.xml
index 11feb207..a83c4129 100755
--- a/indoteknik_custom/views/x_banner_category.xml
+++ b/indoteknik_custom/views/x_banner_category.xml
@@ -23,7 +23,7 @@
<group>
<field name="x_name"/>
<field name="x_banner_subtitle"/>
- <field name="x_studio_field_KKVl4"/>
+ <field name="x_studio_field_KKVl4"/>
<field name="last_update_solr" readonly="1"/>
</group>
<group></group>