summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--indoteknik_api/controllers/api_v1/banner.py30
-rw-r--r--indoteknik_api/controllers/api_v1/flash_sale.py2
-rw-r--r--indoteknik_api/controllers/api_v1/lead.py207
-rw-r--r--indoteknik_api/controllers/api_v1/partner.py590
-rw-r--r--indoteknik_api/controllers/api_v1/product.py87
-rw-r--r--indoteknik_api/controllers/api_v1/sale_order.py30
-rw-r--r--indoteknik_api/controllers/api_v1/stock_picking.py59
-rw-r--r--indoteknik_api/controllers/api_v1/user.py24
-rw-r--r--indoteknik_api/models/product_pricelist.py12
-rw-r--r--indoteknik_api/models/res_partner.py103
-rwxr-xr-xindoteknik_custom/__manifest__.py6
-rw-r--r--indoteknik_custom/controllers/website.py7
-rwxr-xr-xindoteknik_custom/models/__init__.py7
-rw-r--r--indoteknik_custom/models/account_move.py18
-rw-r--r--indoteknik_custom/models/approval_date_doc.py10
-rw-r--r--indoteknik_custom/models/approval_invoice_date.py46
-rw-r--r--indoteknik_custom/models/automatic_purchase.py63
-rw-r--r--indoteknik_custom/models/barcoding_product.py23
-rw-r--r--indoteknik_custom/models/commision.py270
-rw-r--r--indoteknik_custom/models/delivery_order.py7
-rw-r--r--indoteknik_custom/models/invoice_reklas.py6
-rw-r--r--indoteknik_custom/models/logbook_sj.py5
-rw-r--r--indoteknik_custom/models/manufacturing.py7
-rw-r--r--indoteknik_custom/models/mrp_production.py478
-rw-r--r--indoteknik_custom/models/product_pricelist.py1
-rw-r--r--indoteknik_custom/models/product_sla.py133
-rwxr-xr-xindoteknik_custom/models/product_template.py32
-rw-r--r--indoteknik_custom/models/public_holiday.py11
-rwxr-xr-xindoteknik_custom/models/purchase_order.py137
-rw-r--r--indoteknik_custom/models/purchase_order_sales_match.py1
-rwxr-xr-xindoteknik_custom/models/purchase_pricelist.py11
-rw-r--r--indoteknik_custom/models/purchasing_job.py12
-rw-r--r--indoteknik_custom/models/purchasing_job_multi_update.py2
-rw-r--r--indoteknik_custom/models/purchasing_job_state.py3
-rw-r--r--indoteknik_custom/models/res_partner.py247
-rwxr-xr-xindoteknik_custom/models/sale_order.py526
-rw-r--r--indoteknik_custom/models/sale_order_line.py19
-rw-r--r--indoteknik_custom/models/sales_order_koli.py26
-rw-r--r--indoteknik_custom/models/sales_order_reject.py27
-rw-r--r--indoteknik_custom/models/shipment_group.py35
-rw-r--r--indoteknik_custom/models/solr/product_product.py5
-rw-r--r--indoteknik_custom/models/solr/product_template.py9
-rw-r--r--indoteknik_custom/models/solr/x_banner_banner.py3
-rw-r--r--indoteknik_custom/models/stock_backorder_confirmation.py33
-rw-r--r--indoteknik_custom/models/stock_immediate_transfer.py8
-rw-r--r--indoteknik_custom/models/stock_inventory.py26
-rw-r--r--indoteknik_custom/models/stock_move.py31
-rw-r--r--indoteknik_custom/models/stock_picking.py1279
-rw-r--r--indoteknik_custom/models/stock_picking_return.py13
-rw-r--r--indoteknik_custom/models/user_form_merchant.py98
-rw-r--r--indoteknik_custom/models/user_merchant_request.py125
-rw-r--r--indoteknik_custom/models/user_pengajuan_tempo.py3
-rw-r--r--indoteknik_custom/models/user_pengajuan_tempo_request.py48
-rw-r--r--indoteknik_custom/models/vendor_sla.py102
-rw-r--r--indoteknik_custom/models/voucher.py270
-rw-r--r--indoteknik_custom/models/website_user_cart.py121
-rwxr-xr-xindoteknik_custom/models/x_banner_banner.py3
-rwxr-xr-xindoteknik_custom/security/ir.model.access.csv19
-rw-r--r--indoteknik_custom/views/account_move.xml1
-rw-r--r--indoteknik_custom/views/approval_invoice_date.xml92
-rw-r--r--indoteknik_custom/views/barcoding_product.xml5
-rw-r--r--indoteknik_custom/views/customer_commision.xml243
-rw-r--r--indoteknik_custom/views/ir_sequence.xml12
-rw-r--r--indoteknik_custom/views/mrp_production.xml46
-rw-r--r--indoteknik_custom/views/product_pricelist.xml1
-rw-r--r--indoteknik_custom/views/product_product.xml8
-rw-r--r--indoteknik_custom/views/product_sla.xml7
-rwxr-xr-xindoteknik_custom/views/product_template.xml17
-rw-r--r--indoteknik_custom/views/project_views.xml13
-rw-r--r--indoteknik_custom/views/public_holiday.xml55
-rwxr-xr-xindoteknik_custom/views/purchase_order.xml4
-rw-r--r--indoteknik_custom/views/purchasing_job.xml1
-rw-r--r--indoteknik_custom/views/res_partner.xml63
-rwxr-xr-xindoteknik_custom/views/sale_order.xml726
-rw-r--r--indoteknik_custom/views/shipment_group.xml10
-rw-r--r--indoteknik_custom/views/stock_picking.xml124
-rw-r--r--indoteknik_custom/views/user_form_merchant.xml109
-rw-r--r--indoteknik_custom/views/user_merchant_request.xml112
-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_payment_term.xml4
-rw-r--r--indoteknik_custom/views/vendor_sla.xml42
-rwxr-xr-xindoteknik_custom/views/voucher.xml74
-rwxr-xr-xindoteknik_custom/views/x_banner_banner.xml1
-rwxr-xr-xindoteknik_custom/views/x_banner_category.xml2
85 files changed, 6080 insertions, 1213 deletions
diff --git a/indoteknik_api/controllers/api_v1/banner.py b/indoteknik_api/controllers/api_v1/banner.py
index 308d2765..64a6167b 100644
--- a/indoteknik_api/controllers/api_v1/banner.py
+++ b/indoteknik_api/controllers/api_v1/banner.py
@@ -15,7 +15,8 @@ class Banner(controller.Controller):
limit = int(kw.get('limit', 0))
offset = int(kw.get('offset', 0))
order = kw.get('order', 'write_date DESC')
-
+ keyword = kw.get('keyword')
+
query = [('x_status_banner', '=', 'tayang')]
if type:
query += [('x_banner_category.x_studio_field_KKVl4', '=', type)]
@@ -25,9 +26,27 @@ class Banner(controller.Controller):
if manufacture_id:
query += [('x_relasi_manufacture', '=', int(manufacture_id))]
-
- banners = request.env['x_banner.banner'].search(query, limit=limit, offset=offset, order=order)
-
+
+ banner_kumpulan = []
+ banner_ids = set() # Set untuk menyimpan ID banner agar tidak duplikat
+
+ if keyword:
+ keyword_list = [word.strip() for word in keyword.split() if word.strip()] # Pisahkan berdasarkan spasi
+
+ for word in keyword_list:
+ keyword_query = query + [('x_keyword_banner', 'ilike', word)] # Buat query baru dengan keyword
+ banners = request.env['x_banner.banner'].search(keyword_query, limit=limit, offset=offset, order=order)
+
+ for banner in banners:
+ if banner.id not in banner_ids: # Pastikan tidak ada duplikasi
+ banner_kumpulan.append(banner)
+ banner_ids.add(banner.id)
+
+ if not keyword:
+ banners = request.env['x_banner.banner'].search(query, limit=limit, offset=offset, order=order)
+ else:
+ banners = banner_kumpulan if len(banner_kumpulan) > 0 else request.env['x_banner.banner'].search(query, limit=limit, offset=offset, order=order)
+
week_number = self.get_week_number_of_current_month()
end_datas = []
@@ -41,7 +60,8 @@ class Banner(controller.Controller):
'group_by_week': banner.group_by_week,
'image': request.env['ir.attachment'].api_image('x_banner.banner', 'x_banner_image', banner.id),
'headline_banner': banner.x_headline_banner,
- 'description_banner': banner.x_description_banner
+ 'description_banner': banner.x_description_banner,
+ 'keyword_banner': banner.x_keyword_banner
}
if banner.group_by_week and int(banner.group_by_week) < week_number and type == 'index-a-1':
diff --git a/indoteknik_api/controllers/api_v1/flash_sale.py b/indoteknik_api/controllers/api_v1/flash_sale.py
index 6c4ad8c0..1038500c 100644
--- a/indoteknik_api/controllers/api_v1/flash_sale.py
+++ b/indoteknik_api/controllers/api_v1/flash_sale.py
@@ -14,7 +14,7 @@ class FlashSale(controller.Controller):
def _get_flash_sale_header(self, **kw):
try:
# base_url = request.env['ir.config_parameter'].get_param('web.base.url')
- active_flash_sale = request.env['product.pricelist'].get_is_show_program_flash_sale()
+ active_flash_sale = request.env['product.pricelist'].get_is_show_program_flash_sale(is_show_program=kw.get('is_show_program'))
data = []
for pricelist in active_flash_sale:
query = [
diff --git a/indoteknik_api/controllers/api_v1/lead.py b/indoteknik_api/controllers/api_v1/lead.py
index d5cc7c5c..389f36b8 100644
--- a/indoteknik_api/controllers/api_v1/lead.py
+++ b/indoteknik_api/controllers/api_v1/lead.py
@@ -1,6 +1,9 @@
from .. import controller
from odoo import http
from odoo.http import request
+import base64
+import mimetypes
+import json
class Lead(controller.Controller):
@http.route('/api/v1/lead', auth='public', methods=['POST', 'OPTIONS'], csrf=False)
@@ -26,4 +29,206 @@ class Lead(controller.Controller):
lead = request.env['crm.lead'].create(params['value'])
- return self.response(True) \ No newline at end of file
+ return self.response(True)
+
+ @http.route('/api/v1/merchant/<id>', auth='public', methods=['POST', 'OPTIONS'], csrf=False)
+ @controller.Controller.must_authorized()
+ def create_merchant(self, **kw):
+ merchant_request = True if kw.get('merchant_request') == 'true' else False
+ params = self.get_request_params(kw, {
+ # informasi perusahaan
+ "name_merchant": [],
+ "pejabat_name": [],
+ "pic_merchant": [],
+ "pic_position": [],
+ "address": [],
+ "state": ['number'],
+ "city": ['number'],
+ "district": ['number'],
+ "subDistrict": ['number'],
+ "zip": [],
+ "bank_name": [],
+ "rekening_name": [],
+ "account_number": [],
+ "email_company": [],
+ "email_sales": [],
+ "email_finance": [],
+ "phone": [],
+ "mobile": [],
+ "bisnis_type": [],
+ "category_perusahaan": [],
+ "website": [],
+ "description": [],
+
+ # informasi vendor
+ "harga_tayang": [],
+ "merk_dagang": [],
+ "tempo_duration": [],
+ "kredit_limit": [],
+ "is_pengajuan_tempo": [],
+ "waktu_pengiriman": [],
+ "terhitung_sejak": [],
+
+ # Syarat Perdagangan
+ "is_kembali_barang": [],
+ "tempo_garansi": [],
+ "is_order_quantity": [],
+ "explain_garansi": [],
+ "custom_sertifikat_produk": [],
+
+ # # dokumen
+
+ "file_npwp": [],
+ "file_sppkp": [],
+ "file_dokumenKtpDirut ": [],
+ "file_kartuNama": [],
+ "file_suratPernyataan": [],
+ "file_fotoKantor": [],
+ # "file_dataProduk": [],
+ # "file_pricelist": [],
+ })
+ partner_id = int(kw.get('id'))
+ partner = request.env['res.partner'].search([('id', '=', partner_id)], limit=1)
+ main_partner = partner.get_main_parent()
+
+ if params['value']['is_pengajuan_tempo']:
+ if params['value']['is_pengajuan_tempo'] == 'ada':
+ params['value']['is_pengajuan_tempo'] = True
+ else:
+ params['value']['is_pengajuan_tempo'] = False
+
+ if params['value']['is_kembali_barang']:
+ if params['value']['is_kembali_barang'] == 'ya':
+ params['value']['is_kembali_barang'] = kw.get('textReturn')
+ else:
+ params['value']['is_kembali_barang'] = 'Tidak dapat direturn'
+
+ if kw.get('tenggat_waktu'):
+ if kw.get('tenggat_waktu') != 'custom':
+ params['value']['tenggat_waktu'] = kw.get('tenggat_waktu') + ' hari sejak data dikirimkan'
+ else:
+ params['value']['tenggat_waktu'] = kw.get('custom_tenggat_waktu')
+
+ if params['value']['is_order_quantity']:
+ if params['value']['is_order_quantity'] == 'ya':
+ params['value']['is_order_quantity'] = kw.get('minimum_pembelian')
+ else:
+ params['value']['is_order_quantity'] = 'Tidak ada minimum order quantity'
+
+ filtered_params = {key: value for key, value in params['value'].items() if kw.get(key) is not None}
+ form_merchant = request.env['user.form.merchant'].search([('partner_id', '=', main_partner.id)], limit=1)
+ lead = []
+ if form_merchant:
+ form_merchant.write(filtered_params)
+ else:
+ lead = request.env['user.form.merchant'].create(filtered_params)
+ lead.partner_id = main_partner.id
+
+ sertifikat = [
+ 'TKDN',
+ 'SNI',
+ 'K3L',
+ ]
+ sertifikat_ids = kw.get('sertifikat_produk')
+ sertifikat_input = kw.get('custom_sertifikat_produk')
+ dokumen_sertifikat = []
+
+ if sertifikat_ids:
+ dokumen_kirim_ids = list(map(int, sertifikat_ids.split(',')))
+ dokumen_sertifikat = [sertifikat[i] for i in dokumen_kirim_ids if 0 <= i < len(sertifikat)]
+ if sertifikat_input != 'false' and sertifikat_input:
+ input_items = [item.strip() for item in sertifikat_input.split(',')]
+ dokumen_sertifikat.extend(item for item in input_items if item and item not in dokumen_sertifikat)
+ form_merchant.sertifikat_produk = sertifikat_input
+ else:
+ # Jika sertifikat_input kosong, hapus elemen custom (yang tidak ada di daftar `sertifikat`)
+ dokumen_sertifikat = [item for item in dokumen_sertifikat if item in sertifikat]
+ if dokumen_sertifikat:
+ form_merchant.sertifikat_produk = ', '.join(dokumen_sertifikat)
+
+ category_ids = ''
+ category_produk_ids = kw.get('categoryProduk', False)
+ if category_produk_ids:
+ category_ids = list(map(int, category_produk_ids.split(',')))
+ valid_category_ids = request.env['product.public.category'].search([('id', 'in', category_ids)]).ids
+ form_merchant.category_produk_ids = [(6, 0, valid_category_ids)]
+
+ file_dokumen = kw.get('file_dokumen', False)
+ if file_dokumen:
+ form_dokumen = json.loads(file_dokumen)
+
+ for dokumen in form_dokumen:
+ if form_dokumen[dokumen]['details']:
+ mimetype, _ = mimetypes.guess_type(form_dokumen[dokumen]['details']['name'])
+ mimetype = mimetype or 'application/octet-stream'
+ data = base64.b64decode(form_dokumen[dokumen]['details']['format'])
+ sppkp_attachment = request.env['ir.attachment'].create({
+ 'name': form_dokumen[dokumen]['details']['name'],
+ 'type': 'binary',
+ 'datas': base64.b64encode(data),
+ 'res_model': 'user.form.merchant',
+ 'res_id': form_merchant.id,
+ 'mimetype': mimetype
+ })
+
+ if dokumen == 'file_npwp':
+ form_merchant.file_npwp = sppkp_attachment.id
+
+ elif dokumen == 'file_sppkp':
+ form_merchant.file_sppkp = sppkp_attachment.id
+
+ elif dokumen == 'file_dokumenKtpDirut':
+ form_merchant.file_dokumenKtpDirut = sppkp_attachment.id
+
+ elif dokumen == 'file_kartuNama':
+ form_merchant.file_kartuNama = sppkp_attachment.id
+
+ elif dokumen == 'file_suratPernyataan':
+ form_merchant.file_suratPernyataan = sppkp_attachment.id
+
+ elif dokumen == 'file_fotoKantor':
+ form_merchant.file_fotoKantor = sppkp_attachment.id
+
+ elif dokumen == 'file_dataProduk':
+ form_merchant.file_dataProduk = sppkp_attachment.id
+
+ elif dokumen == 'file_pricelist':
+ form_merchant.file_pricelist = sppkp_attachment.id
+ if not params['valid']:
+ return self.response(code=400, description=params)
+ if merchant_request:
+ user_merchant_request = request.env['user.merchant.request'].create({
+ 'user_id': partner.id,
+ 'merchant_id': form_merchant.id,
+ 'user_company_id': main_partner.id
+ })
+
+ return self.response(True)
+
+ @http.route('/api/v1/detail-merchant/<id>', auth='public', methods=['GET', 'OPTIONS'])
+ @controller.Controller.must_authorized()
+ def get_partner_detail_merchant(self, **kw):
+ params = self.get_request_params(kw, {
+ 'id': ['required', 'number']
+ })
+ partner = request.env['res.partner'].search([('id', '=', params['value']['id'])], limit=1)
+ main_partner = partner.get_main_parent()
+ form_merchant = request.env['user.form.merchant'].search([('partner_id', '=', main_partner.id)],limit=1)
+ if not form_merchant:
+ return self.response(code=404, description='form merchant not found')
+ form_merchant = request.env['res.partner'].api_single_response_merchant(form_merchant)
+ return self.response(form_merchant)
+
+ @http.route('/api/v1/check-merchant/<id>', auth='public', methods=['GET', 'OPTIONS'])
+ @controller.Controller.must_authorized()
+ def get_partner_form_merchant(self, **kw):
+ params = self.get_request_params(kw, {
+ 'id': ['required', 'number']
+ })
+ partner = request.env['res.partner'].search([('id', '=', params['value']['id'])], limit=1)
+ main_partner = partner.get_main_parent()
+ form_merchant = request.env['user.merchant.request'].search([('user_company_id', '=', main_partner.id)], limit=1)
+ if form_merchant:
+ return self.response(form_merchant.state_merchant)
+ else:
+ return self.response(code=404, description='form merchant not found') \ No newline at end of file
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..a88c3368 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,70 @@ 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=['POST', 'OPTIONS'] , csrf=False)
@controller.Controller.must_authorized()
+ def get_product_template_sla_by_id(self, **kwargs):
+
+ raw_data = kwargs.get('products', '[]')
+ product_data = json.loads(raw_data)
+
+ product_ids = [int(item["id"]) for item in product_data]
+ products = request.env['purchase.pricelist'].search([
+ ('product_id', 'in', product_ids),
+ ('is_winner', '=', True)
+ ])
+
+ start_date = datetime.today().date()
+ additional_days = request.env['sale.order'].get_days_until_next_business_day(start_date)
+ include_instant = True
+
+ if(len(products) != len(product_ids)):
+ products_data_params = {product["id"] : product for product in product_data }
+
+ all_fast_products = all(
+ product.product_id.qty_free_bandengan >= products_data_params.get(product.product_id.id, {}).get("quantity", 0)
+ for product in products
+ )
+
+ if all_fast_products:
+ return self.response({
+ 'include_instant': include_instant,
+ 'sla_duration': 1,
+ 'sla_additional_days': additional_days,
+ 'sla_total' : int(1) + int(additional_days),
+ 'sla_unit': 'Hari'
+ })
+
+ max_slatime = 1
+
+ for vendor in products:
+ vendor_sla = request.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 self.response({
+ 'include_instant': include_instant,
+ 'sla_duration': max_slatime,
+ 'sla_additional_days': additional_days,
+ 'sla_total' : int(max_slatime) + int(additional_days),
+ 'sla_unit': 'Hari'
+ })
+
+ @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 +110,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 +136,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..d5208fb1 100644
--- a/indoteknik_api/controllers/api_v1/sale_order.py
+++ b/indoteknik_api/controllers/api_v1/sale_order.py
@@ -54,6 +54,20 @@ class SaleOrder(controller.Controller):
# sales = request.env['sale.order'].search_read([('name', '=', sale_number)], fields=['id', 'name', 'amount_total', 'state'])
sales = request.env['sale.order'].search(query, limit=1)
data = []
+ INDONESIAN_MONTHS = {
+ 1: 'Januari',
+ 2: 'Februari',
+ 3: 'Maret',
+ 4: 'April',
+ 5: 'Mei',
+ 6: 'Juni',
+ 7: 'Juli',
+ 8: 'Agustus',
+ 9: 'September',
+ 10: 'Oktober',
+ 11: 'November',
+ 12: 'Desember',
+ }
for sale in sales:
product_name = ''
product_not_in_id = 0
@@ -65,6 +79,7 @@ class SaleOrder(controller.Controller):
'id': sale.id,
'name': sale.name,
'date_order': self.time_to_str(sale.date_order, '%d/%m/%Y %H:%M:%S'),
+ 'expected_ready_to_ship': f"{sale.expected_ready_to_ship.day} {INDONESIAN_MONTHS[sale.expected_ready_to_ship.month]} {sale.expected_ready_to_ship.year}",
'state': sale.state,
'amount_untaxed': sale.amount_untaxed,
'amount_tax': sale.amount_tax,
@@ -142,6 +157,15 @@ class SaleOrder(controller.Controller):
sale_order = request.env['sale.order'].search(domain)
if sale_order:
data = request.env['sale.order'].api_v1_single_response(sale_order, context='with_detail')
+ if sale_order.expected_ready_to_ship:
+ bulan_id = [
+ "Januari", "Februari", "Maret", "April", "Mei", "Juni",
+ "Juli", "Agustus", "September", "Oktober", "November", "Desember"
+ ]
+ tanggal = sale_order.expected_ready_to_ship.day
+ bulan = bulan_id[sale_order.expected_ready_to_ship.month - 1]
+ tahun = sale_order.expected_ready_to_ship.year
+ data['expected_ready_to_ship'] = f"{tanggal} {bulan} {tahun}"
return self.response(data)
@@ -386,7 +410,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 +442,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'],
@@ -464,6 +490,8 @@ class SaleOrder(controller.Controller):
'program_line_id': cart['id'],
'quantity': cart['quantity']
})
+
+ sale_order._compute_etrts_date()
request.env['sale.order.promotion'].create(promotions)
diff --git a/indoteknik_api/controllers/api_v1/stock_picking.py b/indoteknik_api/controllers/api_v1/stock_picking.py
index 2e0c4ad0..31706b99 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'])
@@ -116,10 +116,10 @@ class StockPicking(controller.Controller):
return self.response(picking.get_tracking_detail())
- @http.route(prefix + 'stock-picking/<picking_code>/documentation', auth='public', methods=['PUT', 'OPTIONS'], csrf=False)
+ @http.route(prefix + 'stock-picking/<scanid>/documentation', auth='public', methods=['PUT', 'OPTIONS'], csrf=False)
@controller.Controller.must_authorized()
def write_partner_stock_picking_documentation(self, **kw):
- picking_code = int(kw.get('picking_code', 0))
+ scanid = int(kw.get('scanid', 0))
sj_document = kw.get('sj_document', False)
paket_document = kw.get('paket_document', False)
@@ -128,7 +128,10 @@ class StockPicking(controller.Controller):
'driver_arrival_date': datetime.utcnow(),
}
- picking_data = request.env['stock.picking'].search([('picking_code', '=', picking_code)], limit=1)
+ picking_data = request.env['stock.picking'].search([('id', '=', scanid)], limit=1)
+
+ if not picking_data:
+ picking_data = request.env['stock.picking'].search([('picking_code', '=', scanid)], limit=1)
if not picking_data:
return self.response(code=404, description='picking not found')
@@ -136,4 +139,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/controllers/api_v1/user.py b/indoteknik_api/controllers/api_v1/user.py
index c0974367..967bbcc9 100644
--- a/indoteknik_api/controllers/api_v1/user.py
+++ b/indoteknik_api/controllers/api_v1/user.py
@@ -60,7 +60,9 @@ class User(controller.Controller):
'user': self.response_with_token(user),
}
return self.response(data)
- except:
+ except Exception as e:
+ respon = str(e)
+ print(respon)
return self.response({
'is_auth': False,
'reason': 'NOT_FOUND'
@@ -131,6 +133,7 @@ class User(controller.Controller):
nama_wajib_pajak = kw.get('nama_wajib_pajak', False)
is_pkp = kw.get('is_pkp')
is_terdaftar = kw.get('is_terdaftar', False)
+ is_terdaftar = False if is_terdaftar == 'false' else is_terdaftar
type_acc = kw.get('type_acc', 'individu') or 'individu'
if not name or not email or not password:
@@ -162,9 +165,7 @@ class User(controller.Controller):
'sel_groups_1_9_10': 9
}
- user = request.env['res.users'].create(user_data)
- user.partner_id.email = email
- user.partner_id.mobile = phone
+
if type_acc == 'business' and business_name:
# Eksekusi query SQL menggunakan Levenshtein distance
@@ -182,7 +183,9 @@ class User(controller.Controller):
if result and is_terdaftar:
match_company_name = result[2]
match_company_id = result[0]
-
+ user = request.env['res.users'].create(user_data)
+ user.partner_id.email = email
+ user.partner_id.mobile = phone
# Create a user company request
request.env['user.company.request'].create({
'user_id': user.partner_id.id,
@@ -190,6 +193,9 @@ class User(controller.Controller):
'user_input': business_name
})
else:
+ if not result and is_terdaftar:
+ response['reason'] = 'BISNIS_NOT_FOUND'
+ return self.response(response)
if not nama_wajib_pajak and is_pkp == 'false':
nama_wajib_pajak = business_name
@@ -213,6 +219,10 @@ class User(controller.Controller):
'property_account_payable_id': 438,
'active': False,
}
+
+ user = request.env['res.users'].create(user_data)
+ user.partner_id.email = email
+ user.partner_id.mobile = phone
new_company = request.env['res.partner'].create(new_company_data)
request.env['user.company.request'].create({
'user_id': user.partner_id.id,
@@ -247,8 +257,10 @@ class User(controller.Controller):
'mimetype': sppkp_mimetype
})
new_company.message_post(body="SPPKP Uploaded", attachment_ids=[sppkp_attachment.id])
-
if type_acc == 'individu':
+ user = request.env['res.users'].create(user_data)
+ user.partner_id.email = email
+ user.partner_id.mobile = phone
user.partner_id.customer_type = 'nonpkp'
user.partner_id.npwp = '00.000.000.0-000.000'
user.partner_id.sppkp = '-'
diff --git a/indoteknik_api/models/product_pricelist.py b/indoteknik_api/models/product_pricelist.py
index 6e88517c..e0debf38 100644
--- a/indoteknik_api/models/product_pricelist.py
+++ b/indoteknik_api/models/product_pricelist.py
@@ -95,18 +95,24 @@ class ProductPricelist(models.Model):
], limit=1, order='start_date asc')
return pricelist
- def get_is_show_program_flash_sale(self):
+ def get_is_show_program_flash_sale(self, is_show_program):
"""
Check whether have active flash sale in range of date
@return: returns pricelist: object
"""
+
+ if is_show_program == 'true':
+ is_show_program = True
+ else:
+ is_show_program = False
+
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
pricelist = self.search([
('is_flash_sale', '=', True),
- ('is_show_program', '=', True),
+ ('is_show_program', '=', is_show_program),
('start_date', '<=', current_time),
('end_date', '>=', current_time)
- ], order='start_date asc')
+ ], order='number asc')
return pricelist
def is_flash_sale_product(self, product_id: int):
diff --git a/indoteknik_api/models/res_partner.py b/indoteknik_api/models/res_partner.py
index 0e09fbc6..2cebab83 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,
@@ -167,4 +175,99 @@ class ResPartner(models.Model):
} if pengajuan_tempo.dokumen_tempat_bekerja else '',
}
+ return data
+
+ def api_single_response_merchant(self, form_merchant, with_detail=''):
+ sertifikat = [
+ ['TKDN', '0'],
+ ['SNI', '1'],
+ ['K3L', '2'],
+ ]
+ dokumen_sertifikat = []
+ if form_merchant.sertifikat_produk:
+ form_merchant_dokumen_sertifikat = form_merchant.sertifikat_produk
+ mapping_dokumen = {item[0]: item[1] for item in sertifikat}
+ dokumen_pengiriman_list = [dokumen.strip() for dokumen in form_merchant_dokumen_sertifikat.split(',')]
+ dokumen_sertifikat = [mapping_dokumen.get(dokumen, '3') for dokumen in dokumen_pengiriman_list]
+ data = {
+ 'name_merchant' : form_merchant.name_merchant,
+ 'pejabat_name' : form_merchant.pejabat_name,
+ 'pic_merchant' : form_merchant.pic_merchant,
+ 'pic_position' : form_merchant.pic_position,
+ 'address' : form_merchant.address,
+ 'state' : form_merchant.state.id,
+ 'city' : form_merchant.city.id,
+ 'district' : form_merchant.district.id,
+ 'subDistrict' : form_merchant.subDistrict.id,
+ 'zip' : form_merchant.zip,
+ 'bank_name' : form_merchant.bank_name,
+ 'rekening_name' : form_merchant.rekening_name,
+ 'account_number' : form_merchant.account_number,
+ 'email_company' : form_merchant.email_company,
+ 'email_sales' : form_merchant.email_sales,
+ 'email_finance' : form_merchant.email_finance,
+ 'phone' : form_merchant.phone,
+ 'mobile' : form_merchant.mobile,
+ 'bisnis_type' : form_merchant.bisnis_type,
+ 'category_perusahaan': form_merchant.category_perusahaan,
+ 'website' : form_merchant.website,
+
+ # informasi Vendor
+ 'harga_tayang' : form_merchant.harga_tayang,
+ 'category_produk': ','.join([str(cat.id) for cat in form_merchant.category_produk_ids]) if form_merchant.category_produk_ids else '',
+ 'merk_dagang' : form_merchant.merk_dagang,
+ 'is_pengajuan_tempo' : 'ada' if form_merchant.is_pengajuan_tempo else 'tidak',
+ 'tempo_duration' : form_merchant.tempo_duration.id,
+ 'kredit_limit' : form_merchant.kredit_limit,
+ 'waktu_pengiriman' : form_merchant.waktu_pengiriman,
+ 'terhitung_sejak' : form_merchant.terhitung_sejak,
+
+
+ # syarat perdagangan
+ 'is_kembali_barang': 'tidak' if form_merchant.is_kembali_barang == 'Tidak dapat direturn' else 'ya',
+ 'text_return': form_merchant.is_kembali_barang if form_merchant.is_kembali_barang != 'Tidak dapat direturn' else '',
+ 'tenggat_waktu': form_merchant.tenggat_waktu,
+ 'sertifikat_produk': ','.join(dokumen_sertifikat) if dokumen_sertifikat else '',
+ 'custom_sertifikat_produk': '' if form_merchant.custom_sertifikat_produk == 'false' else form_merchant.custom_sertifikat_produk,
+ 'tempo_garansi': form_merchant.tempo_garansi,
+ 'explain_garansi': form_merchant.explain_garansi,
+ 'is_order_quantity': 'ya' if form_merchant.is_order_quantity != 'Tidak ada minimum order quantity' else 'tidak',
+ 'minimum_pembelian': form_merchant.is_order_quantity,
+
+ #dokumen
+ 'file_npwp':
+ {
+ 'name': form_merchant.file_npwp.name,
+ } if form_merchant.file_npwp else '',
+ 'file_sppkp': {
+ 'name': form_merchant.file_sppkp.name,
+ } if form_merchant.file_sppkp else '',
+ 'file_dokumenKtpDirut':
+ {
+ 'name': form_merchant.file_dokumenKtpDirut.name,
+ }if form_merchant.file_dokumenKtpDirut else '',
+ 'file_kartuNama':
+ {
+ 'name': form_merchant.file_kartuNama.name,
+ }if form_merchant.file_kartuNama else '',
+ 'file_suratPernyataan':
+ {
+ 'name': form_merchant.file_suratPernyataan.name,
+ }if form_merchant.file_suratPernyataan else '',
+ 'file_fotoKantor':
+ {
+ 'name': form_merchant.file_fotoKantor.name
+ }if form_merchant.file_fotoKantor else '',
+ 'file_dataProduk':
+ {
+ 'name': form_merchant.file_dataProduk.name,
+ }if form_merchant.file_dataProduk else '',
+ 'file_pricelist': {
+ 'name': form_merchant.file_pricelist.name,
+ } if form_merchant.file_pricelist else '',
+
+
+
+ }
+
return data \ No newline at end of file
diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py
index f66314fa..d93db041 100755
--- a/indoteknik_custom/__manifest__.py
+++ b/indoteknik_custom/__manifest__.py
@@ -31,6 +31,8 @@
'views/web_logging/web_utm_source.xml',
'views/user_company_request.xml',
'views/user_pengajuan_tempo_request.xml',
+ 'views/user_form_merchant.xml',
+ 'views/user_merchant_request.xml',
'views/vit_kelurahan.xml',
'views/vit_kecamatan.xml',
'views/vit_kota.xml',
@@ -143,6 +145,7 @@
'views/sale_order_multi_uangmuka_penjualan.xml',
'views/shipment_group.xml',
'views/approval_date_doc.xml',
+ 'views/approval_invoice_date.xml',
'views/partner_payment_term.xml',
'views/vendor_payment_term.xml',
'views/approval_unreserve.xml',
@@ -154,6 +157,7 @@
'views/user_pengajuan_tempo.xml',
'views/stock_backorder_confirmation_views.xml',
'views/barcoding_product.xml',
+ 'views/project_views.xml',
'report/report.xml',
'report/report_banner_banner.xml',
'report/report_banner_banner2.xml',
@@ -161,7 +165,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/controllers/website.py b/indoteknik_custom/controllers/website.py
index 2e3df519..120dddad 100644
--- a/indoteknik_custom/controllers/website.py
+++ b/indoteknik_custom/controllers/website.py
@@ -1,7 +1,12 @@
-from odoo.http import request, Controller
+from odoo.http import request, Controller, route
from odoo import http, _
class Website(Controller):
+
+ @route(['/shop', '/shop/cart'], auth='public', website=True)
+ def shop(self, **kw):
+ return request.redirect('https://indoteknik.com/shop/promo', code=302)
+
@http.route('/content', auth='public')
def content(self, **kw):
url = kw.get('url', '')
diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py
index 3573eddd..e17f68d1 100755
--- a/indoteknik_custom/models/__init__.py
+++ b/indoteknik_custom/models/__init__.py
@@ -139,9 +139,16 @@ 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 user_form_merchant
+from . import user_merchant_request
from . import barcoding_product
+from . import sales_order_koli
+from . import stock_backorder_confirmation
from . import account_payment_register
from . import stock_inventory
+from . import approval_invoice_date
diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py
index 9aa0743b..30de67be 100644
--- a/indoteknik_custom/models/account_move.py
+++ b/indoteknik_custom/models/account_move.py
@@ -66,6 +66,19 @@ class AccountMove(models.Model):
other_taxes = fields.Float(string="Other Taxes", compute='compute_other_taxes')
is_hr = fields.Boolean(string="Is HR?", default=False)
purchase_order_id = fields.Many2one('purchase.order', string='Purchase Order')
+ length_of_payment = fields.Integer(string="Length of Payment", compute='compute_length_of_payment')
+
+ def compute_length_of_payment(self):
+ for rec in self:
+ payment_term = rec.invoice_payment_term_id.line_ids[0].days
+ terima_faktur = rec.date_terima_tukar_faktur
+ payment = self.search([('ref', '=', rec.name), ('move_type', '=', 'entry')], limit=1)
+
+ if payment and terima_faktur:
+ date_diff = terima_faktur - payment.date
+ rec.length_of_payment = date_diff.days + payment_term
+ else:
+ rec.length_of_payment = 0
def _update_line_name_from_ref(self):
"""Update all account.move.line name fields with ref from account.move"""
@@ -76,11 +89,12 @@ 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:
- rec.other_subtotal = rec.amount_untaxed * (11 / 12)
+ rec.other_subtotal = round(rec.amount_untaxed * (11 / 12))
@api.model
def generate_attachment(self, record):
diff --git a/indoteknik_custom/models/approval_date_doc.py b/indoteknik_custom/models/approval_date_doc.py
index 751bae82..638b44d7 100644
--- a/indoteknik_custom/models/approval_date_doc.py
+++ b/indoteknik_custom/models/approval_date_doc.py
@@ -39,12 +39,16 @@ class ApprovalDateDoc(models.Model):
if not self.env.user.is_accounting:
raise UserError("Hanya Accounting Yang Bisa Approve")
self.check_invoice_so_picking
- self.picking_id.driver_departure_date = self.driver_departure_date
- self.picking_id.date_doc_kirim = self.driver_departure_date
+ # Tambahkan context saat mengupdate date_doc_kirim
+ self.picking_id.with_context(from_button_approve=True).write({
+ 'driver_departure_date': self.driver_departure_date,
+ 'date_doc_kirim': self.driver_departure_date,
+ 'update_date_doc_kirim_add': True
+ })
self.state = 'done'
self.approve_date = datetime.utcnow()
self.approve_by = self.env.user.id
-
+
def button_cancel(self):
self.state = 'cancel'
diff --git a/indoteknik_custom/models/approval_invoice_date.py b/indoteknik_custom/models/approval_invoice_date.py
new file mode 100644
index 00000000..48546e55
--- /dev/null
+++ b/indoteknik_custom/models/approval_invoice_date.py
@@ -0,0 +1,46 @@
+from odoo import models, api, fields
+from odoo.exceptions import AccessError, UserError, ValidationError
+from datetime import timedelta, date, datetime
+import logging
+
+_logger = logging.getLogger(__name__)
+
+class ApprovalInvoiceDate(models.Model):
+ _name = "approval.invoice.date"
+ _description = "Approval Invoice Date"
+ _rec_name = 'number'
+
+ picking_id = fields.Many2one('stock.picking', string='Picking')
+ number = fields.Char(string='Document No', index=True, copy=False, readonly=True, tracking=True)
+ date_invoice = fields.Datetime(
+ string='Invoice Date',
+ copy=False
+ )
+ date_doc_do = fields.Datetime(
+ string='Tanggal Kirim di SJ',
+ copy=False
+ )
+ state = fields.Selection([('draft', 'Draft'), ('done', 'Done'), ('cancel', 'Cancel')], string='State', default='draft', tracking=True)
+ approve_date = fields.Datetime(string='Approve Date', copy=False)
+ approve_by = fields.Many2one('res.users', string='Approve By', copy=False)
+ sale_id = fields.Many2one('sale.order', string='Sale Order')
+ partner_id = fields.Many2one('res.partner', string='Partner')
+ move_id = fields.Many2one('account.move', string='Invoice')
+ note = fields.Char(string='Note')
+
+ def button_approve(self):
+ if not self.env.user.is_accounting:
+ raise UserError("Hanya Accounting Yang Bisa Approve")
+ self.move_id.invoice_date = self.date_doc_do
+ self.state = 'done'
+ self.approve_date = datetime.utcnow()
+ self.approve_by = self.env.user.id
+
+ def button_cancel(self):
+ self.state = 'cancel'
+
+ @api.model
+ def create(self, vals):
+ vals['number'] = self.env['ir.sequence'].next_by_code('approval.invoice.date') or '0'
+ result = super(ApprovalInvoiceDate, self).create(vals)
+ return result
diff --git a/indoteknik_custom/models/automatic_purchase.py b/indoteknik_custom/models/automatic_purchase.py
index fbdf8dae..b66121e1 100644
--- a/indoteknik_custom/models/automatic_purchase.py
+++ b/indoteknik_custom/models/automatic_purchase.py
@@ -1,4 +1,4 @@
-from odoo import models, api, fields
+from odoo import models, api, fields, tools
from odoo.exceptions import UserError
from datetime import datetime
import logging, math
@@ -67,6 +67,15 @@ class AutomaticPurchase(models.Model):
if count > 0:
raise UserError('Ada sekitar %s SO Yang sudah create PO, berikut SO nya: %s' % (count, ', '.join(names)))
+
+ def unlink_note_pj(self):
+ product = self.purchase_lines.mapped('product_id')
+ pj_state = self.env['purchasing.job.state'].search([
+ ('purchasing_job_id', 'in', product.ids)
+ ])
+
+ for line in pj_state:
+ line.unlink()
def create_po_from_automatic_purchase(self):
if not self.purchase_lines:
@@ -75,6 +84,7 @@ class AutomaticPurchase(models.Model):
raise UserError('Sudah pernah di create PO')
current_time = datetime.now()
+ self.unlink_note_pj()
vendor_ids = self.env['automatic.purchase.line'].read_group(
[('automatic_purchase_id', '=', self.id), ('partner_id', '!=', False)],
fields=['partner_id'],
@@ -284,7 +294,7 @@ class AutomaticPurchase(models.Model):
def create_purchase_order_sales_match(self, purchase_order):
matches_so_product_ids = [line.product_id.id for line in purchase_order.order_line]
- matches_so = self.env['automatic.purchase.sales.match'].search([
+ matches_so = self.env['v.sale.notin.matchpo'].search([
('automatic_purchase_id', '=', self.id),
('sale_line_id.product_id', 'in', matches_so_product_ids),
])
@@ -292,6 +302,8 @@ class AutomaticPurchase(models.Model):
sale_ids_set = set()
sale_ids_name = set()
for sale_order in matches_so:
+ # @stephan skip so line yang sudah pernah ada di purchase order sales match sebelumnya
+
salesperson_name = sale_order.sale_id.user_id.name
sale_id_with_salesperson = f"{sale_order.sale_id.name} - {salesperson_name}"
@@ -655,3 +667,50 @@ class SyncPurchasingJob(models.Model):
outgoing = fields.Float(string="Outgoing")
action = fields.Char(string="Status")
date = fields.Datetime(string="Date Sync")
+
+
+class SaleNotInMatchPO(models.Model):
+ # created by @stephan for speed up performance while create po from automatic purchase
+ _name = 'v.sale.notin.matchpo'
+ _auto = False
+ _rec_name = 'id'
+
+ id = fields.Integer()
+ automatic_purchase_id = fields.Many2one('automatic.purchase', string='APO')
+ automatic_purchase_line_id = fields.Many2one('automatic.purchase.line', string='APO Line')
+ sale_id = fields.Many2one('sale.order', string='Sale')
+ sale_line_id = fields.Many2one('sale.order.line', string='Sale Line')
+ picking_id = fields.Many2one('stock.picking', string='Picking')
+ move_id = fields.Many2one('stock.move', string='Move')
+ partner_id = fields.Many2one('res.partner', string='Partner')
+ partner_invoice_id = fields.Many2one('res.partner', string='Partner Invoice')
+ salesperson_id = fields.Many2one('res.user', string='Salesperson')
+ product_id = fields.Many2one('product.product', string='Product')
+ qty_so = fields.Float(string='Qty SO')
+ qty_po = fields.Float(string='Qty PO')
+ create_uid = fields.Many2one('res.user', string='Created By')
+ create_date = fields.Datetime(string='Create Date')
+ write_uid = fields.Many2one('res.user', string='Updated By')
+ write_date = fields.Many2one(string='Updated')
+ purchase_price = fields.Many2one(string='Purchase Price')
+ purchase_tax_id = fields.Many2one('account.tax', string='Purchase Tax')
+ note_procurement = fields.Many2one(string='Note Procurement')
+
+ def init(self):
+ tools.drop_view_if_exists(self.env.cr, self._table)
+ self.env.cr.execute("""
+ CREATE OR REPLACE VIEW %s AS(
+ select apsm.id, apsm.automatic_purchase_id, apsm.automatic_purchase_line_id, apsm.sale_id, apsm.sale_line_id,
+ apsm.picking_id, apsm.move_id, apsm.partner_id,
+ apsm.partner_invoice_id, apsm.salesperson_id, apsm.product_id, apsm.qty_so, apsm.qty_po, apsm.create_uid,
+ apsm.create_date, apsm.write_uid, apsm.write_date, apsm.purchase_price,
+ apsm.purchase_tax_id, apsm.note_procurement
+ from automatic_purchase_sales_match apsm
+ where apsm.sale_line_id not in (
+ select distinct coalesce(posm.sale_line_id,0)
+ from purchase_order_sales_match posm
+ join purchase_order po on po.id = posm.purchase_order_id
+ where po.state not in ('cancel')
+ )
+ )
+ """ % self._table)
diff --git a/indoteknik_custom/models/barcoding_product.py b/indoteknik_custom/models/barcoding_product.py
index e1b8f41f..204c6128 100644
--- a/indoteknik_custom/models/barcoding_product.py
+++ b/indoteknik_custom/models/barcoding_product.py
@@ -12,15 +12,32 @@ class BarcodingProduct(models.Model):
barcoding_product_line = fields.One2many('barcoding.product.line', 'barcoding_product_id', string='Barcoding Product Lines', auto_join=True)
product_id = fields.Many2one('product.product', string="Product", tracking=3)
quantity = fields.Float(string="Quantity", tracking=3)
- type = fields.Selection([('print', 'Print Barcode'), ('barcoding', 'Add Barcode To Product')], string='Type', default='print')
+ type = fields.Selection([('print', 'Print Barcode'), ('barcoding', 'Add Barcode To Product'), ('barcoding_box', 'Add Barcode Box To Product')], string='Type', default='print')
barcode = fields.Char(string="Barcode")
+ qty_pcs_box = fields.Char(string="Quantity Pcs Box")
+
+ def check_duplicate_barcode(self):
+ if self.type in ['barcoding_box', 'barcoding']:
+ barcode_product = self.env['product.product'].search([('barcode', '=', self.barcode)])
+
+ if barcode_product:
+ raise UserError('Barcode sudah digunakan {}'.format(barcode_product.display_name))
+
+ barcode_box = self.env['product.product'].search([('barcode_box', '=', self.barcode)])
+
+ if barcode_box:
+ raise UserError('Barcode box sudah digunakan {}'.format(barcode_box.display_name))
@api.constrains('barcode')
def _send_barcode_to_product(self):
for record in self:
- if record.barcode and not record.product_id.barcode:
+ record.check_duplicate_barcode()
+ if record.type == 'barcoding_box':
+ record.product_id.barcode_box = record.barcode
+ record.product_id.qty_pcs_box = record.qty_pcs_box
+ else:
record.product_id.barcode = record.barcode
-
+
@api.onchange('product_id', 'quantity')
def _onchange_product_or_quantity(self):
"""Update barcoding_product_line based on product_id and quantity"""
diff --git a/indoteknik_custom/models/commision.py b/indoteknik_custom/models/commision.py
index 6920154a..eeaa8efc 100644
--- a/indoteknik_custom/models/commision.py
+++ b/indoteknik_custom/models/commision.py
@@ -1,7 +1,10 @@
-from odoo import models, api, fields
+from odoo import models, api, fields, _
from odoo.exceptions import UserError
from datetime import datetime
+# import datetime
import logging
+from terbilang import Terbilang
+import pytz
_logger = logging.getLogger(__name__)
@@ -12,8 +15,10 @@ class CustomerRebate(models.Model):
_inherit = ['mail.thread']
partner_id = fields.Many2one('res.partner', string='Customer', required=True)
- date_from = fields.Date(string='Date From', required=True, help="Pastikan tanggal 1 januari, jika tidak, code akan break")
- date_to = fields.Date(string='Date To', required=True, help="Pastikan tanggal 31 desember, jika tidak, code akan break")
+ date_from = fields.Date(string='Date From', required=True,
+ help="Pastikan tanggal 1 januari, jika tidak, code akan break")
+ date_to = fields.Date(string='Date To', required=True,
+ help="Pastikan tanggal 31 desember, jika tidak, code akan break")
description = fields.Char(string='Description')
target_1st = fields.Float(string='Target/Quarter 1st')
target_2nd = fields.Float(string='Target/Quarter 2nd')
@@ -35,7 +40,7 @@ class CustomerRebate(models.Model):
line.dpp_q2 = line._get_current_dpp_q2(line)
line.dpp_q3 = line._get_current_dpp_q3(line)
line.dpp_q4 = line._get_current_dpp_q4(line)
-
+
def _compute_achievement(self):
for line in self:
line.status_q1 = line._check_achievement(line.target_1st, line.target_2nd, line.dpp_q1)
@@ -52,18 +57,18 @@ class CustomerRebate(models.Model):
else:
status = 'not achieve'
return status
-
+
def _get_current_dpp_q1(self, line):
sum_dpp = 0
brand = [10, 89, 122]
where = [
- ('move_id.move_type', '=', 'out_invoice'),
- ('move_id.state', '=', 'posted'),
- ('move_id.is_customer_commision', '=', False),
- ('move_id.partner_id.id', '=', line.partner_id.id),
- ('move_id.invoice_date', '>=', line.date_from),
- ('move_id.invoice_date', '<=', '2023-03-31'),
- ('product_id.x_manufacture', 'in', brand),
+ ('move_id.move_type', '=', 'out_invoice'),
+ ('move_id.state', '=', 'posted'),
+ ('move_id.is_customer_commision', '=', False),
+ ('move_id.partner_id.id', '=', line.partner_id.id),
+ ('move_id.invoice_date', '>=', line.date_from),
+ ('move_id.invoice_date', '<=', '2023-03-31'),
+ ('product_id.x_manufacture', 'in', brand),
]
invoice_lines = self.env['account.move.line'].search(where, order='id')
for invoice_line in invoice_lines:
@@ -74,13 +79,13 @@ class CustomerRebate(models.Model):
sum_dpp = 0
brand = [10, 89, 122]
where = [
- ('move_id.move_type', '=', 'out_invoice'),
- ('move_id.state', '=', 'posted'),
- ('move_id.is_customer_commision', '=', False),
- ('move_id.partner_id.id', '=', line.partner_id.id),
- ('move_id.invoice_date', '>=', '2023-04-01'),
- ('move_id.invoice_date', '<=', '2023-06-30'),
- ('product_id.x_manufacture', 'in', brand),
+ ('move_id.move_type', '=', 'out_invoice'),
+ ('move_id.state', '=', 'posted'),
+ ('move_id.is_customer_commision', '=', False),
+ ('move_id.partner_id.id', '=', line.partner_id.id),
+ ('move_id.invoice_date', '>=', '2023-04-01'),
+ ('move_id.invoice_date', '<=', '2023-06-30'),
+ ('product_id.x_manufacture', 'in', brand),
]
invoices = self.env['account.move.line'].search(where, order='id')
for invoice in invoices:
@@ -91,13 +96,13 @@ class CustomerRebate(models.Model):
sum_dpp = 0
brand = [10, 89, 122]
where = [
- ('move_id.move_type', '=', 'out_invoice'),
- ('move_id.state', '=', 'posted'),
- ('move_id.is_customer_commision', '=', False),
- ('move_id.partner_id.id', '=', line.partner_id.id),
- ('move_id.invoice_date', '>=', '2023-07-01'),
- ('move_id.invoice_date', '<=', '2023-09-30'),
- ('product_id.x_manufacture', 'in', brand),
+ ('move_id.move_type', '=', 'out_invoice'),
+ ('move_id.state', '=', 'posted'),
+ ('move_id.is_customer_commision', '=', False),
+ ('move_id.partner_id.id', '=', line.partner_id.id),
+ ('move_id.invoice_date', '>=', '2023-07-01'),
+ ('move_id.invoice_date', '<=', '2023-09-30'),
+ ('product_id.x_manufacture', 'in', brand),
]
invoices = self.env['account.move.line'].search(where, order='id')
for invoice in invoices:
@@ -108,13 +113,13 @@ class CustomerRebate(models.Model):
sum_dpp = 0
brand = [10, 89, 122]
where = [
- ('move_id.move_type', '=', 'out_invoice'),
- ('move_id.state', '=', 'posted'),
- ('move_id.is_customer_commision', '=', False),
- ('move_id.partner_id.id', '=', line.partner_id.id),
- ('move_id.invoice_date', '>=', '2023-10-01'),
- ('move_id.invoice_date', '<=', line.date_to),
- ('product_id.x_manufacture', 'in', brand),
+ ('move_id.move_type', '=', 'out_invoice'),
+ ('move_id.state', '=', 'posted'),
+ ('move_id.is_customer_commision', '=', False),
+ ('move_id.partner_id.id', '=', line.partner_id.id),
+ ('move_id.invoice_date', '>=', '2023-10-01'),
+ ('move_id.invoice_date', '<=', line.date_to),
+ ('product_id.x_manufacture', 'in', brand),
]
invoices = self.env['account.move.line'].search(where, order='id')
for invoice in invoices:
@@ -122,6 +127,22 @@ class CustomerRebate(models.Model):
return sum_dpp
+class RejectReasonCommision(models.TransientModel):
+ _name = 'reject.reason.commision'
+ _description = 'Wizard for Reject Reason Customer Commision'
+
+ request_id = fields.Many2one('customer.commision', string='Request')
+ reason_reject = fields.Text(string='Reason for Rejection', required=True, tracking=True)
+
+ def confirm_reject(self):
+ commision = self.request_id
+ if commision:
+ commision.last_status = commision.status
+ commision.write({'reason_reject': self.reason_reject})
+ commision.status = 'reject'
+ return {'type': 'ir.actions.act_window_close'}
+
+
class CustomerCommision(models.Model):
_name = 'customer.commision'
_order = 'id desc'
@@ -134,29 +155,132 @@ class CustomerCommision(models.Model):
partner_ids = fields.Many2many('res.partner', String='Customer', required=True)
description = fields.Char(string='Description')
notification = fields.Char(string='Notification')
- commision_lines = fields.One2many('customer.commision.line', 'customer_commision_id', string='Lines', auto_join=True)
+ commision_lines = fields.One2many('customer.commision.line', 'customer_commision_id', string='Lines',
+ auto_join=True)
status = fields.Selection([
- ('pengajuan1', 'Menunggu Approval Marketing'),
- ('pengajuan2', 'Menunggu Approval Pimpinan'),
- ('approved', 'Approved')
- ], string='Status', copy=False, readonly=True, tracking=3)
+ ('draft', 'Draft'),
+ ('pengajuan1', 'Menunggu Approval Manager Sales'),
+ ('pengajuan2', 'Menunggu Approval Marketing'),
+ ('pengajuan3', 'Menunggu Approval Pimpinan'),
+ ('pengajuan4', 'Menunggu Approval Accounting'),
+ ('approved', 'Approved'),
+ ('reject', 'Rejected'),
+ ], string='Status', copy=False, readonly=True, tracking=3, index=True, track_visibility='onchange', default='draft')
+ last_status = fields.Selection([
+ ('draft', 'Draft'),
+ ('pengajuan1', 'Menunggu Approval Manager Sales'),
+ ('pengajuan2', 'Menunggu Approval Marketing'),
+ ('pengajuan3', 'Menunggu Approval Pimpinan'),
+ ('pengajuan4', 'Menunggu Approval Accounting'),
+ ('approved', 'Approved'),
+ ('reject', 'Rejected'),
+ ], string='Status')
commision_percent = fields.Float(string='Commision %', tracking=3)
commision_amt = fields.Float(string='Commision Amount', tracking=3)
+ commision_amt_text = fields.Char(string='Commision Amount Text', compute='compute_delivery_amt_text')
total_dpp = fields.Float(string='Total DPP', compute='_compute_total_dpp')
commision_type = fields.Selection([
('fee', 'Fee'),
('cashback', 'Cashback'),
('rebate', 'Rebate'),
], string='Commision Type', required=True)
- bank_name = fields.Char(string='Bank', tracking=3)
- account_name = fields.Char(string='Account Name', tracking=3)
- bank_account = fields.Char(string='Account No', tracking=3)
+ bank_name = fields.Char(string='Bank', tracking=3, required=True)
+ account_name = fields.Char(string='Account Name', tracking=3, required=True)
+ bank_account = fields.Char(string='Account No', tracking=3, required=True)
note_transfer = fields.Char(string='Keterangan')
brand_ids = fields.Many2many('x_manufactures', string='Brands')
payment_status = fields.Selection([
('pending', 'Pending'),
('payment', 'Payment'),
], string='Payment Status', copy=False, readonly=True, tracking=3, default='pending')
+ note_finnance = fields.Text('Notes Finnance')
+ reason_reject = fields.Char(string='Reason Reaject', tracking=True, track_visibility='onchange')
+ approved_by = fields.Char(string='Approved By', tracking=True, track_visibility='always')
+
+ 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')
+
+ sales_id = fields.Many2one('res.users', string="Sales", tracking=True, default=lambda self: self.env.user,
+ domain=lambda self: [
+ ('groups_id', 'in', self.env.ref('sales_team.group_sale_salesman').id)])
+
+ date_approved_sales = fields.Datetime(string="Date Approved Sales", tracking=True)
+ date_approved_marketing = fields.Datetime(string="Date Approved Marketing", tracking=True)
+ date_approved_pimpinan = fields.Datetime(string="Date Approved Pimpinan", tracking=True)
+ date_approved_accounting = fields.Datetime(string="Date Approved Accounting", tracking=True)
+
+ position_sales = fields.Char(string="Position Sales", tracking=True)
+ position_marketing = fields.Char(string="Position Marketing", tracking=True)
+ position_pimpinan = fields.Char(string="Position Pimpinan", tracking=True)
+ position_accounting = fields.Char(string="Position Accounting", tracking=True)
+
+ # get partner ids so it can be grouped by
+ @api.model
+ def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
+ if 'partner_ids' in groupby:
+ # Get all records matching the domain
+ records = self.search(domain)
+
+ # Create groups for each partner
+ groups = {}
+ for record in records:
+ for partner in record.partner_ids:
+ if partner.id not in groups:
+ groups[partner.id] = {
+ 'partner_ids': partner,
+ 'records': self.env['customer.commision']
+ }
+ groups[partner.id]['records'] |= record
+
+ # Format the result
+ result = []
+ for partner_id, group_data in groups.items():
+ partner = group_data['partner_ids']
+ record_ids = group_data['records'].ids
+
+ # Create the domain
+ group_domain = [('id', 'in', record_ids)]
+ if domain:
+ group_domain = ['&'] + domain + group_domain
+
+ result.append({
+ 'partner_ids': (partner.id, partner.display_name),
+ 'partner_ids_count': len(record_ids),
+ '__domain': group_domain,
+ '__count': len(record_ids),
+ })
+
+ return result
+
+ return super(CustomerCommision, self).read_group(domain, fields, groupby, offset, limit, orderby, lazy)
+
+ def compute_delivery_amt_text(self):
+ tb = Terbilang()
+
+ for record in self:
+ res = ''
+
+ try:
+ if record.commision_amt > 0:
+ tb.parse(int(record.commision_amt))
+ res = tb.getresult().title()
+ record.commision_amt_text = res + ' Rupiah'
+ except:
+ record.commision_amt_text = res
+
+ 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?
@@ -182,22 +306,25 @@ class CustomerCommision(models.Model):
self.commision_percent = achieve_2nd
else:
self.commision_percent = 0
-
+
self._onchange_commision_amt()
@api.constrains('commision_percent')
def _onchange_commision_percent(self):
if not self.env.context.get('_onchange_commision_percent', True):
return
-
+
if self.commision_amt == 0:
self.commision_amt = self.commision_percent * self.total_dpp // 100
-
+
@api.constrains('commision_amt')
def _onchange_commision_amt(self):
+ """
+ Constrain to update commision percent from commision amount
+ """
if not self.env.context.get('_onchange_commision_amt', True):
return
-
+
if self.total_dpp > 0 and self.commision_percent == 0:
self.commision_percent = (self.commision_amt / self.total_dpp) * 100
@@ -219,19 +346,54 @@ class CustomerCommision(models.Model):
result = super(CustomerCommision, self).create(vals)
return result
- def action_confirm_customer_commision(self):#add 2 step approval
- if not self.status:
+ def action_confirm_customer_commision(self):
+ jakarta_tz = pytz.timezone('Asia/Jakarta')
+ now = datetime.now(jakarta_tz)
+
+ now_naive = now.replace(tzinfo=None)
+
+ if not self.status or self.status == 'draft':
self.status = 'pengajuan1'
- elif self.status == 'pengajuan1' and self.env.user.id == 19:
+ elif self.status == 'pengajuan1' and self.env.user.is_sales_manager:
self.status = 'pengajuan2'
- elif self.status == 'pengajuan2' and self.env.user.is_leader:
+ self.approved_by = (self.approved_by + ', ' if self.approved_by else '') + self.env.user.name
+ self.date_approved_sales = now_naive
+ self.position_sales = 'Sales Manager'
+ elif self.status == 'pengajuan2' and self.env.user.id == 19:
+ self.status = 'pengajuan3'
+ self.approved_by = (self.approved_by + ', ' if self.approved_by else '') + self.env.user.name
+ self.date_approved_marketing = now_naive
+ self.position_marketing = 'Marketing Manager'
+ elif self.status == 'pengajuan3' and self.env.user.is_leader:
+ self.status = 'pengajuan4'
+ self.approved_by = (self.approved_by + ', ' if self.approved_by else '') + self.env.user.name
+ self.date_approved_pimpinan = now_naive
+ self.position_pimpinan = 'Pimpinan'
+ elif self.status == 'pengajuan4' and self.env.user.id == 1272:
for line in self.commision_lines:
line.invoice_id.is_customer_commision = True
self.status = 'approved'
+ self.approved_by = (self.approved_by + ', ' if self.approved_by else '') + self.env.user.name
+ self.date_approved_accounting = now_naive
+ self.position_accounting = 'Accounting'
else:
raise UserError('Harus di approved oleh yang bersangkutan')
return
+ def action_reject(self): # add 2 step approval
+ return {
+ 'type': 'ir.actions.act_window',
+ 'name': _('Reject Reason'),
+ 'res_model': 'reject.reason.commision',
+ 'view_mode': 'form',
+ 'target': 'new',
+ 'context': {'default_request_id': self.id},
+ }
+
+ def button_draft(self):
+ for commision in self:
+ commision.status = commision.last_status if commision.last_status else 'draft'
+
def action_confirm_customer_payment(self):
if self.status != 'approved':
raise UserError('Commision harus di approve terlebih dahulu sebelum di konfirmasi pembayarannya')
@@ -249,7 +411,7 @@ class CustomerCommision(models.Model):
def generate_customer_commision(self):
if self.commision_lines:
raise UserError('Line sudah ada, tidak bisa di generate ulang')
-
+
if self.commision_type == 'fee':
self._generate_customer_commision_fee()
else:
@@ -324,11 +486,13 @@ class CustomerCommision(models.Model):
}])
return
+
class CustomerCommisionLine(models.Model):
_name = 'customer.commision.line'
_order = 'id'
- customer_commision_id = fields.Many2one('customer.commision', string='Ref', required=True, ondelete='cascade', copy=False)
+ customer_commision_id = fields.Many2one('customer.commision', string='Ref', required=True, ondelete='cascade',
+ copy=False)
invoice_id = fields.Many2one('account.move', string='Invoice')
partner_id = fields.Many2one('res.partner', string='Customer')
state = fields.Char(string='InvStatus')
@@ -336,7 +500,11 @@ class CustomerCommisionLine(models.Model):
tax = fields.Float(string='TaxAmt')
total = fields.Float(string='Total')
total_percent_margin = fields.Float('Total Margin', related='invoice_id.sale_id.total_percent_margin')
+ total_margin_excl_third_party = fields.Float('Before Margin',
+ related='invoice_id.sale_id.total_margin_excl_third_party')
product_id = fields.Many2one('product.product', string='Product')
+ sale_order_id = fields.Many2one('sale.order', string='Sale Order', related='invoice_id.sale_id')
+
class AccountMove(models.Model):
_inherit = 'account.move'
diff --git a/indoteknik_custom/models/delivery_order.py b/indoteknik_custom/models/delivery_order.py
index 3473197b..2dd0c802 100644
--- a/indoteknik_custom/models/delivery_order.py
+++ b/indoteknik_custom/models/delivery_order.py
@@ -25,7 +25,8 @@ class DeliveryOrder(models.TransientModel):
picking = False
if delivery_order_line[2]['name']:
picking = self.env['stock.picking'].search([('picking_code', '=', delivery_order_line[2]['name'])], limit=1)
-
+ if not picking:
+ picking = self.env['stock.picking'].search([('out_code', '=', delivery_order_line[2]['name'])], limit=1)
if picking:
line_tracking_no = delivery_order_line[2]['tracking_no']
@@ -86,6 +87,10 @@ class DeliveryOrderLine(models.TransientModel):
if len(self.name) == 13:
self.name = self.name[:-1]
picking = self.env['stock.picking'].search([('picking_code', '=', self.name)], limit=1)
+
+ if not picking:
+ picking = self.env['stock.picking'].search([('out_code', '=', self.name)], limit=1)
+
if picking:
if picking.driver_id:
self.driver_id = picking.driver_id
diff --git a/indoteknik_custom/models/invoice_reklas.py b/indoteknik_custom/models/invoice_reklas.py
index f5bb5a25..d10d4c31 100644
--- a/indoteknik_custom/models/invoice_reklas.py
+++ b/indoteknik_custom/models/invoice_reklas.py
@@ -18,6 +18,12 @@ class InvoiceReklas(models.TransientModel):
('pembelian', 'Pembelian'),
], string='Reklas Tipe')
+ @api.onchange('reklas_type')
+ def _onchange_reklas_type(self):
+ if self.reklas_type == 'penjualan':
+ invoices = self.env['account.move'].browse(self._context.get('active_ids', []))
+ self.pay_amt = invoices.amount_total
+
def create_reklas(self):
if not self.reklas_type:
raise UserError('Reklas Tipe harus diisi')
diff --git a/indoteknik_custom/models/logbook_sj.py b/indoteknik_custom/models/logbook_sj.py
index 9f349882..75b2622f 100644
--- a/indoteknik_custom/models/logbook_sj.py
+++ b/indoteknik_custom/models/logbook_sj.py
@@ -26,6 +26,8 @@ class LogbookSJ(models.TransientModel):
report_logbook = self.env['report.logbook.sj'].create([parameters_header])
for line in logbook_line:
picking = self.env['stock.picking'].search([('picking_code', '=', line.name)], limit=1)
+ if not picking:
+ picking = self.env['stock.picking'].search([('out_code', '=', line.name)], limit=1)
stock = picking
parent_id = stock.partner_id.parent_id.id
parent_id = parent_id if parent_id else stock.partner_id.id
@@ -80,6 +82,9 @@ class LogbookSJLine(models.TransientModel):
if len(self.name) == 13:
self.name = self.name[:-1]
picking = self.env['stock.picking'].search([('picking_code', '=', self.name)], limit=1)
+
+ if not picking:
+ picking = self.env['stock.picking'].search([('out_code', '=', self.name)], limit=1)
if picking:
if picking.driver_id:
self.driver_id = picking.driver_id
diff --git a/indoteknik_custom/models/manufacturing.py b/indoteknik_custom/models/manufacturing.py
index 24a8b8c3..715d8513 100644
--- a/indoteknik_custom/models/manufacturing.py
+++ b/indoteknik_custom/models/manufacturing.py
@@ -26,6 +26,13 @@ class Manufacturing(models.Model):
# Check product category
if self.product_id.categ_id.name != 'Finish Good':
raise UserError('Tidak bisa di complete karna product category bukan Unit / Finish Good')
+
+ if self.sale_order and self.sale_order.state != 'sale':
+ raise UserError(
+ ('Tidak bisa Mark as Done.\nSales Order "%s" (Nomor: %s) belum dikonfirmasi.')
+ % (self.sale_order.partner_id.name, self.sale_order.name)
+ )
+
for line in self.move_raw_ids:
# if line.quantity_done > 0 and line.quantity_done != self.product_uom_qty:
# raise UserError('Qty Consume per Line tidak sama dengan Qty to Produce')
diff --git a/indoteknik_custom/models/mrp_production.py b/indoteknik_custom/models/mrp_production.py
index 54d90256..8179fe56 100644
--- a/indoteknik_custom/models/mrp_production.py
+++ b/indoteknik_custom/models/mrp_production.py
@@ -1,10 +1,484 @@
-from odoo import fields, models, api, _
+from odoo import models, fields, api, tools, _
+from datetime import datetime, timedelta
+import math
+import logging
from odoo.exceptions import AccessError, UserError, ValidationError
class MrpProduction(models.Model):
_inherit = 'mrp.production'
+ check_bom_product_lines = fields.One2many('check.bom.product', 'production_id', string='Check Product', auto_join=True, copy=False)
desc = fields.Text(string='Description')
+ sale_order = fields.Many2one('sale.order', string='Sale Order', copy=False)
+ production_purchase_match = fields.One2many('production.purchase.match', 'production_id', string='Purchase Matches', auto_join=True)
+ is_po = fields.Boolean(string='Is PO')
+ state_reserve = fields.Selection([
+ ('waiting', 'Waiting For Fullfilment'),
+ ('ready', 'Ready to Ship'),
+ ('done', 'Done'),
+ ('cancel', 'Cancelled'),
+ ], string='Status Reserve', tracking=True, copy=False, help="The current state of the stock picking.")
+ date_reserved = fields.Datetime(string="Date Reserved", help='Tanggal ter-reserved semua barang nya', copy=False)
+
+
+ @api.constrains('check_bom_product_lines')
+ def constrains_check_bom_product_lines(self):
+ for rec in self:
+ if len(rec.check_bom_product_lines) > 0:
+ rec.qty_producing = rec.product_qty
+
+ def button_mark_done(self):
+ """Override button_mark_done untuk mengirim pesan ke Sale Order jika state berubah menjadi 'confirmed'."""
+ if self._name != 'mrp.production':
+ return super(MrpProduction, self).button_mark_done()
+
+ result = super(MrpProduction, self).button_mark_done()
+
+ for record in self:
+ if len(record.check_bom_product_lines) < 1:
+ raise UserError("Check Product Tidak Boleh Kosong")
+ if not record.sale_order:
+ raise UserError("Sale Order Tidak Boleh Kosong")
+ if record.sale_order and record.state == 'confirmed':
+ message = _("Manufacturing order telah dibuat dengan nomor %s") % (record.name)
+ record.sale_order.message_post(body=message)
+
+ return result
+
+ def action_confirm(self):
+ """Override action_confirm untuk mengirim pesan ke Sale Order jika state berubah menjadi 'confirmed'."""
+ if self._name != 'mrp.production':
+ return super(MrpProduction, self).action_confirm()
+
+ result = super(MrpProduction, self).action_confirm()
+
+ for record in self:
+ # if len(record.check_bom_product_lines) < 1:
+ # raise UserError("Check Product Tidak Boleh Kosong")
+ if record.sale_order and record.state == 'confirmed':
+ message = _("Manufacturing order telah dibuat dengan nomor %s") % (record.name)
+ record.sale_order.message_post(body=message)
+
+ return result
+
+
+ def create_po_from_manufacturing(self):
+ if not self.state == 'confirmed':
+ raise UserError('Harus Di Approve oleh Merchandiser')
+
+ if self.is_po == True:
+ raise UserError('Sudah pernah di buat PO')
+
+ if not self.move_raw_ids:
+ raise UserError('Tidak ada Lines, belum bisa create PO')
+ # if self.is_po:
+ # raise UserError('Sudah pernah di create PO')
+
+ vendor_ids = self.env['stock.move'].read_group([
+ ('raw_material_production_id', '=', self.id),
+ ('vendor_id', '!=', False)
+ ], fields=['vendor_id'], groupby=['vendor_id'])
+
+ po_ids = []
+ for vendor in vendor_ids:
+ result_po = self.create_po_by_vendor(vendor['vendor_id'][0])
+ po_ids += result_po
+ return {
+ 'name': _('Purchase Order'),
+ 'view_mode': 'tree,form',
+ 'res_model': 'purchase.order',
+ 'target': 'current',
+ 'type': 'ir.actions.act_window',
+ 'domain': [('id', 'in', po_ids)],
+ }
- \ No newline at end of file
+
+ def create_po_by_vendor(self, vendor_id):
+ current_time = datetime.now()
+
+ PRODUCT_PER_PO = 20
+
+ stock_move = self.env['stock.move']
+
+ param_header = {
+ 'partner_id': vendor_id,
+ # 'partner_ref': self.sale_order_id.name,
+ 'currency_id': 12,
+ 'user_id': self.env.user.id,
+ 'company_id': 1, # indoteknik dotcom gemilang
+ 'picking_type_id': 28, # indoteknik bandengan receipts
+ 'date_order': current_time,
+ 'product_bom_id': self.product_id.id,
+ # 'sale_order_id': self.sale_order_id.id,
+ 'note_description': 'from Manufacturing Order'
+ }
+
+ domain = [
+ ('raw_material_production_id', '=', self.id),
+ ('vendor_id', '=', vendor_id),
+ ('state', 'in', ['waiting','confirmed','partially_available'])
+ ]
+
+ products_len = stock_move.search_count(domain)
+ page = math.ceil(products_len / PRODUCT_PER_PO)
+ po_ids = []
+ # i start from zero (0)
+ for i in range(page):
+ new_po = self.env['purchase.order'].create([param_header])
+ new_po.name = new_po.name + "/MO/" + str(i + 1)
+ po_ids.append(new_po.id)
+ lines = stock_move.search(
+ domain,
+ offset=i * PRODUCT_PER_PO,
+ limit=PRODUCT_PER_PO
+ )
+ tax = [22]
+
+ for line in lines:
+ product = line.product_id
+ price, taxes, vendor = self._get_purchase_price(product)
+
+ param_line = {
+ 'order_id' : new_po.id,
+ 'product_id': product.id,
+ 'product_qty': line.product_uom_qty if line.state in ['confirmed', 'waiting'] else line.product_uom_qty - line.forecast_availability,
+ 'product_uom_qty': line.product_uom_qty if line.state in ['confirmed', 'waiting'] else line.product_uom_qty - line.forecast_availability,
+ 'name': product.display_name,
+ 'price_unit': price if price else 0.0,
+ 'taxes_id': [taxes] if taxes else [],
+ }
+ new_po_line = self.env['purchase.order.line'].create([param_line])
+
+ self.env['production.purchase.match'].create([{
+ 'production_id': self.id,
+ 'order_id': new_po.id
+ }])
+
+ self.is_po = True
+
+ return po_ids
+
+ def _get_purchase_price(self, product_id):
+ override_vendor = product_id.x_manufacture.override_vendor_id
+ query = [('product_id', '=', product_id.id),
+ ('vendor_id', '=', override_vendor.id)]
+ purchase_price = self.env['purchase.pricelist'].search(query, limit=1)
+ if purchase_price:
+ return self._get_valid_purchase_price(purchase_price)
+ else:
+ purchase_price = self.env['purchase.pricelist'].search(
+ [('product_id', '=', product_id.id),
+ ('is_winner', '=', True)],
+ limit=1)
+
+ return self._get_valid_purchase_price(purchase_price)
+
+ def _get_valid_purchase_price(self, purchase_price):
+ current_time = datetime.now()
+ delta_time = current_time - timedelta(days=365)
+ # delta_time = delta_time.strftime('%Y-%m-%d %H:%M:%S')
+
+ price = 0
+ taxes = ''
+ vendor_id = ''
+ human_last_update = purchase_price.human_last_update or datetime.min
+ system_last_update = purchase_price.system_last_update or datetime.min
+
+ if purchase_price.taxes_product_id.type_tax_use == 'purchase':
+ price = purchase_price.product_price
+ taxes = purchase_price.taxes_product_id.id
+ vendor_id = purchase_price.vendor_id.id
+ if delta_time > human_last_update:
+ price = 0
+ taxes = ''
+ vendor_id = ''
+
+ if system_last_update > human_last_update:
+ if purchase_price.taxes_system_id.type_tax_use == 'purchase':
+ price = purchase_price.system_price
+ taxes = purchase_price.taxes_system_id.id
+ vendor_id = purchase_price.vendor_id.id
+ if delta_time > system_last_update:
+ price = 0
+ taxes = ''
+ vendor_id = ''
+
+ return price, taxes, vendor_id
+
+class CheckBomProduct(models.Model):
+ _name = 'check.bom.product'
+ _description = 'Check Product'
+ _order = 'production_id, id'
+
+ production_id = fields.Many2one(
+ 'mrp.production',
+ string='Bom Reference',
+ required=True,
+ ondelete='cascade',
+ index=True,
+ copy=False,
+ )
+ product_id = fields.Many2one('product.product', string='Product')
+ quantity = fields.Float(string='Quantity')
+ status = fields.Char(string='Status', compute='_compute_status')
+ code_product = fields.Char(string='Code Product')
+
+ @api.constrains('production_id')
+ def _check_missing_components(self):
+ for mo in self:
+ required = mo.production_id.move_raw_ids.mapped('product_id')
+ entered = mo.production_id.check_bom_product_lines.mapped('product_id')
+ missing = required - entered
+
+ # Jika HTML tidak bekerja sama sekali, gunakan format text biasa yang rapi
+ if missing:
+ product_list = "\n- " + "\n- ".join(p.display_name for p in missing)
+ raise UserError(
+ "⚠️ Komponen Wajib Diisi\n\n"
+ "Produk berikut harus ditambahkan:\n"
+ f"{product_list}\n\n"
+ "Silakan lengkapi terlebih dahulu."
+ )
+
+ @api.constrains('production_id', 'product_id')
+ def _check_product_bom_validation(self):
+ for record in self:
+ if not record.production_id or not record.product_id:
+ continue
+
+ moves = record.production_id.move_raw_ids.filtered(
+ lambda move: move.product_id.id == record.product_id.id
+ )
+
+ if not moves:
+ raise UserError((
+ "The product '%s' tidak ada di operations. "
+ ) % record.product_id.display_name)
+
+ total_qty_in_moves = sum(moves.mapped('product_uom_qty'))
+
+ # Find existing lines for the same product, excluding the current line
+ existing_lines = record.production_id.check_bom_product_lines.filtered(
+ lambda line: line.product_id == record.product_id
+ )
+
+ if existing_lines:
+ total_quantity = sum(existing_lines.mapped('quantity'))
+
+ if total_quantity < total_qty_in_moves:
+ raise UserError((
+ "Quantity Product '%s' kurang dari quantity demand."
+ ) % (record.product_id.display_name))
+ else:
+ # Check if the quantity exceeds the allowed total
+ if record.quantity < total_qty_in_moves:
+ raise UserError((
+ "Quantity Product '%s' kurang dari quantity demand."
+ ) % (record.product_id.display_name))
+
+ # Set the quantity to the entered value
+ record.quantity = record.quantity
+
+ @api.onchange('code_product')
+ def _onchange_code_product(self):
+ if not self.code_product:
+ return
+
+ # Cari product berdasarkan default_code, barcode, atau barcode_box
+ product = self.env['product.product'].search([
+ '|',
+ ('default_code', '=', self.code_product),
+ '|',
+ ('barcode', '=', self.code_product),
+ ('barcode_box', '=', self.code_product)
+ ], limit=1)
+
+ if not product:
+ raise UserError("Product tidak ditemukan")
+
+ # Jika scan barcode_box, set quantity sesuai qty_pcs_box
+ if product.barcode_box == self.code_product:
+ self.product_id = product.id
+ self.quantity = product.qty_pcs_box
+ self.code_product = product.default_code or product.barcode
+ # return {
+ # 'warning': {
+ # 'title': 'Info',8994175025871
+
+ # 'message': f'Product box terdeteksi. Quantity di-set ke {product.qty_pcs_box}'
+ # }
+ # }
+ else:
+ # Jika scan biasa
+ self.product_id = product.id
+ self.code_product = product.default_code or product.barcode
+ self.quantity = 1
+
+ def unlink(self):
+ # Get all affected pickings before deletion
+ productions = self.mapped('production_id')
+
+ # Store product_ids that will be deleted
+ deleted_product_ids = self.mapped('product_id')
+
+ # Perform the deletion
+ result = super(CheckBomProduct, self).unlink()
+
+ # After deletion, update moves for affected pickings
+ for production in productions:
+ # For products that were completely removed (no remaining check.bom.product lines)
+ remaining_product_ids = production.check_bom_product_lines.mapped('product_id')
+ removed_product_ids = deleted_product_ids - remaining_product_ids
+
+ # Set quantity_done to 0 for moves of completely removed products
+ moves_to_reset = production.move_raw_ids.filtered(
+ lambda move: move.product_id in removed_product_ids
+ )
+ for move in moves_to_reset:
+ move.quantity_done = 0.0
+
+ production.qty_producing = 0
+
+ # Also sync remaining products in case their totals changed
+ self._sync_check_product_to_moves(production)
+
+ return result
+
+ @api.depends('quantity')
+ def _compute_status(self):
+ for record in self:
+ moves = record.production_id.move_raw_ids.filtered(
+ lambda move: move.product_id.id == record.product_id.id
+ )
+ total_qty_in_moves = sum(moves.mapped('product_uom_qty'))
+
+ if record.quantity < total_qty_in_moves:
+ record.status = 'Pending'
+ else:
+ record.status = 'Done'
+
+
+ def create(self, vals):
+ # Create the record
+ record = super(CheckBomProduct, self).create(vals)
+ # Ensure uniqueness after creation
+ if not self.env.context.get('skip_consolidate'):
+ record.with_context(skip_consolidate=True)._consolidate_duplicate_lines()
+ return record
+
+ def write(self, vals):
+ # Write changes to the record
+ result = super(CheckBomProduct, self).write(vals)
+ # Ensure uniqueness after writing
+ if not self.env.context.get('skip_consolidate'):
+ self.with_context(skip_consolidate=True)._consolidate_duplicate_lines()
+ return result
+
+ def _sync_check_product_to_moves(self, production):
+ """
+ Sinkronisasi quantity_done di move_raw_ids
+ dengan total quantity dari check.bom.product berdasarkan product_id.
+ """
+ for product_id in production.check_bom_product_lines.mapped('product_id'):
+ # Totalkan quantity dari semua baris check.bom.product untuk product_id ini
+ total_quantity = sum(
+ line.quantity for line in production.check_bom_product_lines.filtered(lambda line: line.product_id == product_id)
+ )
+ # Update quantity_done di move yang relevan
+ moves = production.move_raw_ids.filtered(lambda move: move.product_id == product_id)
+ for move in moves:
+ move.quantity_done = total_quantity
+
+ def _consolidate_duplicate_lines(self):
+ """
+ Consolidate duplicate lines with the same product_id under the same production_id
+ and sync the total quantity to related moves.
+ """
+ for production in self.mapped('production_id'):
+ lines_to_remove = self.env['check.bom.product'] # Recordset untuk menyimpan baris yang akan dihapus
+ product_lines = production.check_bom_product_lines.filtered(lambda line: line.product_id)
+
+ # Group lines by product_id
+ product_groups = {}
+ for line in product_lines:
+ product_groups.setdefault(line.product_id.id, []).append(line)
+
+ for product_id, lines in product_groups.items():
+ if len(lines) > 1:
+ # Consolidate duplicate lines
+ first_line = lines[0]
+ total_quantity = sum(line.quantity for line in lines)
+
+ # Update the first line's quantity
+ first_line.with_context(skip_consolidate=True).write({'quantity': total_quantity})
+
+ # Add the remaining lines to the lines_to_remove recordset
+ lines_to_remove |= self.env['check.bom.product'].browse([line.id for line in lines[1:]])
+
+ # Perform unlink after consolidation
+ if lines_to_remove:
+ lines_to_remove.unlink()
+
+ # Sync total quantities to moves
+ self._sync_check_product_to_moves(production)
+
+ @api.onchange('product_id', 'quantity')
+ def check_product_validity(self):
+ for record in self:
+ if not record.production_id or not record.product_id:
+ continue
+
+ # Filter moves related to the selected product
+ moves = record.production_id.move_raw_ids.filtered(
+ lambda move: move.product_id.id == record.product_id.id
+ )
+
+ if not moves:
+ raise UserError((
+ "The product '%s' tidak ada di operations. "
+ ) % record.product_id.display_name)
+
+ total_qty_in_moves = sum(moves.mapped('product_uom_qty'))
+
+ # Find existing lines for the same product, excluding the current line
+ existing_lines = record.production_id.check_bom_product_lines.filtered(
+ lambda line: line.product_id == record.product_id
+ )
+
+ if existing_lines:
+ # Get the first existing line
+ first_line = existing_lines[0]
+
+ # Calculate the total quantity after addition
+ total_quantity = sum(existing_lines.mapped('quantity'))
+
+ if total_quantity > total_qty_in_moves:
+ raise UserError((
+ "Quantity Product '%s' sudah melebihi quantity demand."
+ ) % (record.product_id.display_name))
+ else:
+ # Check if the quantity exceeds the allowed total
+ if record.quantity == total_qty_in_moves:
+ raise UserError((
+ "Quantity Product '%s' sudah melebihi quantity demand."
+ ) % (record.product_id.display_name))
+
+ # Set the quantity to the entered value
+ record.quantity = record.quantity
+
+
+class ProductionPurchaseMatch(models.Model):
+ _name = 'production.purchase.match'
+ _order = 'production_id, id'
+
+ production_id = fields.Many2one('mrp.production', string='Ref', required=True, ondelete='cascade', index=True, copy=False)
+ order_id = fields.Many2one('purchase.order', string='Purchase Order')
+ vendor = fields.Char(string='Vendor', compute='_compute_info_po')
+ total = fields.Float(string='Total', compute='_compute_info_po')
+
+ def _compute_info_po(self):
+ for match in self:
+ match.vendor = match.order_id.partner_id.name
+ match.total = match.order_id.amount_total
+
diff --git a/indoteknik_custom/models/product_pricelist.py b/indoteknik_custom/models/product_pricelist.py
index c299ff2f..ea3ee6cf 100644
--- a/indoteknik_custom/models/product_pricelist.py
+++ b/indoteknik_custom/models/product_pricelist.py
@@ -17,6 +17,7 @@ class ProductPricelist(models.Model):
], string='Flash Sale Option')
banner_top = fields.Binary(string='Banner Top')
flashsale_tag = fields.Char(string='Flash Sale Tag')
+ number = fields.Integer(string='Sequence')
def _check_end_date_and_update_solr(self):
today = datetime.utcnow().date()
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..a09570f4 100755
--- a/indoteknik_custom/models/product_template.py
+++ b/indoteknik_custom/models/product_template.py
@@ -15,6 +15,14 @@ _logger = logging.getLogger(__name__)
class ProductTemplate(models.Model):
_inherit = "product.template"
+
+ image_carousel_lines = fields.One2many(
+ comodel_name="image.carousel",
+ inverse_name="product_id",
+ string="Image Carousel",
+ auto_join=True,
+ copy=False
+ )
x_studio_field_tGhJR = fields.Many2many('x_product_tags', string="Product Tags")
x_manufacture = fields.Many2one(
comodel_name="x_manufactures",
@@ -246,7 +254,7 @@ class ProductTemplate(models.Model):
# product.default_code = 'ITV.'+str(product.id)
# _logger.info('Updated Variant %s' % product.name)
- @api.onchange('name','default_code','x_manufacture','product_rating','website_description','image_1920','weight','public_categ_ids')
+ @api.onchange('name','default_code','x_manufacture','product_rating','website_description','image_1920','weight','public_categ_ids','image_carousel_lines')
def update_solr_flag(self):
for tmpl in self:
if tmpl.solr_flag == 1:
@@ -421,7 +429,21 @@ class ProductProduct(models.Model):
plafon_qty = fields.Float(string='Max Plafon', compute='_get_plafon_qty_product')
merchandise_ok = fields.Boolean(string='Product Promotion')
qr_code_variant = fields.Binary("QR Code Variant", compute='_compute_qr_code_variant')
+ qty_pcs_box = fields.Float("Pcs Box")
+ barcode_box = fields.Char("Barcode Box")
+
+ 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
@@ -720,3 +742,11 @@ class OutstandingMove(models.Model):
'partially_available'
)
""" % self._table)
+
+class ImageCarousel(models.Model):
+ _name = 'image.carousel'
+ _description = 'Image Carousel'
+ _order = 'product_id, id'
+
+ product_id = fields.Many2one('product.template', string='Product', required=True, ondelete='cascade', index=True, copy=False)
+ image = fields.Binary(string='Image')
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/purchase_order.py b/indoteknik_custom/models/purchase_order.py
index d90c4a8a..98b367d0 100755
--- a/indoteknik_custom/models/purchase_order.py
+++ b/indoteknik_custom/models/purchase_order.py
@@ -74,6 +74,7 @@ class PurchaseOrder(models.Model):
date_done_picking = fields.Datetime(string='Date Done Picking', compute='get_date_done')
bills_dp_id = fields.Many2one('account.move', string='Bills DP')
bills_pelunasan_id = fields.Many2one('account.move', string='Bills Pelunasan')
+ product_bom_id = fields.Many2one('product.product', string='Product Bom')
grand_total = fields.Monetary(string='Grand Total', help='Amount total + amount delivery', compute='_compute_grand_total')
total_margin_match = fields.Float(string='Total Margin Match', compute='_compute_total_margin_match')
approve_by = fields.Many2one('res.users', string='Approve By')
@@ -88,6 +89,112 @@ class PurchaseOrder(models.Model):
store_name = fields.Char(string='Nama Toko')
purchase_order_count = fields.Integer('Purchase Order Count', related='partner_id.purchase_order_count')
+ # cek payment term
+ def _check_payment_term(self):
+ _logger.info("Check Payment Term Terpanggil")
+
+ cbd_term = self.env['account.payment.term'].search([
+ ('name', 'ilike', 'Cash Before Delivery')
+ ], limit=1)
+
+ for order in self:
+ if not order.partner_id or not order.partner_id.minimum_amount:
+ continue
+
+ if not order.order_line or order.amount_total == 0:
+ continue
+
+ if order.amount_total < order.partner_id.minimum_amount:
+ if cbd_term and order.payment_term_id != cbd_term:
+ order.payment_term_id = cbd_term.id
+ self.env.user.notify_info(
+ message="Total belanja PO belum mencapai minimum yang ditentukan vendor. "
+ "Payment Term telah otomatis diubah menjadi Cash Before Delivery (C.B.D).",
+ title="Payment Term Diperbarui"
+ )
+ else:
+ vendor_term = order.partner_id.property_supplier_payment_term_id
+ if vendor_term and order.payment_term_id != vendor_term:
+ order.payment_term_id = vendor_term.id
+ self.env.user.notify_info(
+ message=f"Total belanja PO telah memenuhi jumlah minimum vendor. "
+ f"Payment Term otomatis dikembalikan ke pengaturan vendor awal: *{vendor_term.name}*.",
+ title="Payment Term Diperbarui"
+ )
+
+ def _check_tax_rule(self):
+ _logger.info("Check Tax Rule Terpanggil")
+
+ # Pajak 11%
+ tax_11 = self.env['account.tax'].search([
+ ('type_tax_use', '=', 'purchase'),
+ ('name', 'ilike', '11%')
+ ], limit=1)
+
+ # Pajak "No Tax"
+ no_tax = self.env['account.tax'].search([
+ ('type_tax_use', '=', 'purchase'),
+ ('name', 'ilike', 'no tax')
+ ], limit=1)
+
+ if not tax_11:
+ raise UserError("Pajak 11% tidak ditemukan. Mohon pastikan pajak 11% tersedia.")
+
+ if not no_tax:
+ raise UserError("Pajak 'No Tax' tidak ditemukan. Harap buat tax dengan nama 'No Tax' dan tipe 'Purchase'.")
+
+ for order in self:
+ partner = order.partner_id
+ minimum_tax = partner.minimum_amount_tax
+
+ _logger.info("Partner ID: %s, Minimum Tax: %s, Untaxed Total: %s", partner.id, minimum_tax, order.amount_untaxed)
+
+ if not minimum_tax or not order.order_line:
+ continue
+
+ if order.amount_total < minimum_tax:
+ _logger.info(">>> Total di bawah minimum → apply No Tax")
+ for line in order.order_line:
+ line.taxes_id = [(6, 0, [no_tax.id])]
+
+ if self.env.context.get('notify_tax'):
+ self.env.user.notify_info(
+ message="Total belanja PO belum mencapai minimum pajak vendor. "
+ "Pajak diganti menjadi 'No Tax'.",
+ title="Pajak Diperbarui",
+ )
+ else:
+ _logger.info(">>> Total memenuhi minimum → apply Pajak 11%")
+ for line in order.order_line:
+ line.taxes_id = [(6, 0, [tax_11.id])]
+
+ if self.env.context.get('notify_tax'):
+ self.env.user.notify_info(
+ message="Total belanja sebelum pajak telah memenuhi minimum. "
+ "Pajak 11%% diterapkan",
+ title="Pajak Diperbarui",
+ )
+
+ # set default no_tax pada order line
+ # @api.onchange('order_line')
+ # def _onchange_order_line_tax_default(self):
+ # _logger.info("Onchange Order Line Tax Default Terpanggil")
+
+ # no_tax = self.env['account.tax'].search([
+ # ('type_tax_use', '=', 'purchase'),
+ # ('name', 'ilike', 'no tax')
+ # ], limit=1)
+
+ # if not no_tax:
+ # _logger.info("No Tax tidak ditemukan")
+ # return
+
+ # for order in self:
+ # for line in order.order_line:
+ # if not line.taxes_id:
+ # line.taxes_id = [(6, 0, [no_tax.id])]
+ # _logger.info("Auto-set No tax ke baris product: %s", line.product_id.name)
+
@api.onchange('total_cost_service')
def _onchange_total_cost_service(self):
for order in self:
@@ -671,6 +778,7 @@ class PurchaseOrder(models.Model):
raise UserError("Produk "+line.product_id.name+" memiliki vendor berbeda dengan SO (Vendor PO: "+str(self.partner_id.name)+", Vendor SO: "+str(line.so_line_id.vendor_id.name)+")")
def button_confirm(self):
+ # self._check_payment_term() # check payment term
res = super(PurchaseOrder, self).button_confirm()
current_time = datetime.now()
self.check_ppn_mix()
@@ -726,9 +834,25 @@ class PurchaseOrder(models.Model):
self.unlink_purchasing_job_state()
self._check_qty_plafon_product()
+ if self.product_bom_id:
+ self._remove_product_bom()
return res
+ def _remove_product_bom(self):
+ pj = self.env['v.purchasing.job'].search([
+ ('product_id', '=', self.product_bom_id.id)
+ ])
+
+ if pj:
+ pj_state = self.env['purchasing.job.state'].search([
+ ('purchasing_job_id', '=', pj.id)
+ ])
+
+ if pj_state:
+ pj_state.note = 'Product BOM Sudah Di PO'
+ pj_state.date_po = datetime.utcnow()
+
def check_ppn_mix(self):
reference_taxes = self.order_line[0].taxes_id
@@ -1062,6 +1186,19 @@ class PurchaseOrder(models.Model):
return super(PurchaseOrder, self).button_unlock()
+ @api.model #override custom create & write for check payment term
+ def create(self, vals):
+ order = super().create(vals)
+ # order.with_context(skip_check_payment=True)._check_payment_term()
+ # order.with_context(notify_tax=True)._check_tax_rule()
+ return order
+
+ def write(self, vals):
+ res = super().write(vals)
+ if not self.env.context.get('skip_check_payment'):
+ self.with_context(skip_check_payment=True)._check_payment_term()
+ self.with_context(notify_tax=True)._check_tax_rule()
+ return res
class PurchaseOrderUnlockWizard(models.TransientModel):
_name = 'purchase.order.unlock.wizard'
diff --git a/indoteknik_custom/models/purchase_order_sales_match.py b/indoteknik_custom/models/purchase_order_sales_match.py
index ed013dd5..0bd0092b 100644
--- a/indoteknik_custom/models/purchase_order_sales_match.py
+++ b/indoteknik_custom/models/purchase_order_sales_match.py
@@ -27,6 +27,7 @@ class PurchaseOrderSalesMatch(models.Model):
purchase_price_so = fields.Float(string='Purchase Price Sale Order', related='sale_line_id.purchase_price')
purchase_price_po = fields.Float('Purchase Price PO', compute='_compute_purchase_price_po')
purchase_line_id = fields.Many2one('purchase.order.line', string='Purchase Line', compute='_compute_purchase_line_id')
+ hold_outgoing_so = fields.Boolean(string='Hold Outgoing SO', related='sale_id.hold_outgoing')
def _compute_purchase_line_id(self):
for line in self:
diff --git a/indoteknik_custom/models/purchase_pricelist.py b/indoteknik_custom/models/purchase_pricelist.py
index e5b35d7f..dd680f4d 100755
--- a/indoteknik_custom/models/purchase_pricelist.py
+++ b/indoteknik_custom/models/purchase_pricelist.py
@@ -83,6 +83,15 @@ class PurchasePricelist(models.Model):
massage="Ada duplikat product dan vendor, berikut data yang anda duplikat : \n" + str(existing_purchase.product_id.name) + " - " + str(existing_purchase.vendor_id.name) + " - " + str(existing_purchase.product_price)
if existing_purchase:
raise UserError(massage)
+
+ def sync_pricelist_item_promo(self, product):
+ pricelist_product = self.env['product.pricelist.item'].search([('product_id', '=', product.id), ('pricelist_id', '=', 17022)])
+ for pricelist in pricelist_product:
+ if pricelist.fixed_price == 0:
+ flashsale = self.env['product.pricelist.item'].search([('product_id', '=', product.id), ('pricelist_id.is_flash_sale', '=', True)])
+ if flashsale:
+ flashsale.fixed_price = 0
+ return
def action_calculate_pricelist(self):
MAX_PRICELIST = 10
@@ -94,6 +103,8 @@ class PurchasePricelist(models.Model):
records = self.env['purchase.pricelist'].browse(active_ids)
price_group = self.env['price.group'].collect_price_group()
for rec in records:
+ if rec.include_price == 0:
+ rec.sync_pricelist_item_promo(rec.product_id)
product_group = rec.product_id.product_tmpl_id.x_manufacture.pricing_group or None
price_incl = rec.include_price
diff --git a/indoteknik_custom/models/purchasing_job.py b/indoteknik_custom/models/purchasing_job.py
index 902bc34b..ea2f46cb 100644
--- a/indoteknik_custom/models/purchasing_job.py
+++ b/indoteknik_custom/models/purchasing_job.py
@@ -25,6 +25,15 @@ class PurchasingJob(models.Model):
], string='APO?')
purchase_representative_id = fields.Many2one('res.users', string="Purchase Representative", readonly=True)
note = fields.Char(string="Note Detail")
+ date_po = fields.Datetime(string='Date PO', copy=False)
+
+ def unlink(self):
+ # Example: Delete related records from the underlying model
+ underlying_records = self.env['purchasing.job'].search([
+ ('product_id', 'in', self.mapped('product_id').ids)
+ ])
+ underlying_records.unlink()
+ return super(PurchasingJob, self).unlink()
def redirect_to_pjs(self):
states = self.env['purchasing.job.state'].search([
@@ -56,8 +65,9 @@ class PurchasingJob(models.Model):
pmp.action,
max(pjs.status_apo::text) AS status_apo,
max(pjs.note::text) AS note,
+ max(pjs.date_po::text) AS date_po,
CASE
- WHEN pmp.brand IN ('Tekiro', 'RYU', 'Rexco') THEN 27
+ WHEN pmp.brand IN ('Tekiro', 'RYU', 'Rexco', 'RYU (Sparepart)') THEN 27
WHEN sub.vendor_id = 9688 THEN 397
WHEN sub.vendor_id = 35475 THEN 397
WHEN sub.vendor_id = 29712 THEN 397
diff --git a/indoteknik_custom/models/purchasing_job_multi_update.py b/indoteknik_custom/models/purchasing_job_multi_update.py
index deba960a..80a43e45 100644
--- a/indoteknik_custom/models/purchasing_job_multi_update.py
+++ b/indoteknik_custom/models/purchasing_job_multi_update.py
@@ -18,7 +18,7 @@ class PurchasingJobMultiUpdate(models.TransientModel):
('purchasing_job_id', '=', product.id)
])
- purchasing_job_state.unlink()
+ # purchasing_job_state.unlink()
purchasing_job_state.create({
'purchasing_job_id': product.id,
diff --git a/indoteknik_custom/models/purchasing_job_state.py b/indoteknik_custom/models/purchasing_job_state.py
index 1838a496..d014edfe 100644
--- a/indoteknik_custom/models/purchasing_job_state.py
+++ b/indoteknik_custom/models/purchasing_job_state.py
@@ -14,4 +14,5 @@ class PurchasingJobState(models.Model):
('not_apo', 'Belum APO'),
('apo', 'APO')
], string='APO?', copy=False)
- note = fields.Char(string="Note Detail")
+ note = fields.Char(string="Note Detail", copy=False)
+ date_po = fields.Datetime(string='Date PO', copy=False)
diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py
index 7e574a72..ff07c94c 100644
--- a/indoteknik_custom/models/res_partner.py
+++ b/indoteknik_custom/models/res_partner.py
@@ -2,6 +2,7 @@ from odoo import models, fields, api
from odoo.exceptions import UserError, ValidationError
from datetime import datetime
from odoo.http import request
+import re
class GroupPartner(models.Model):
_name = 'group.partner'
@@ -39,6 +40,14 @@ class ResPartner(models.Model):
estimasi_tempo = fields.Char(string='Estimasi Pembelian Pertahun')
tempo_duration = fields.Many2one('account.payment.term', string='Durasi Tempo')
tempo_limit = fields.Char(string='Limit Tempo')
+ minimum_amount = fields.Float(
+ string="Minimum Order",
+ help="Jika total belanja kurang dari ini, maka payment term akan otomatis menjadi CBD."
+ )
+ minimum_amount_tax = fields.Float(
+ string="Minimum Amount Tax",
+ help="Jika total belanja kurang dari ini, maka tax akan otomatis menjadi 0%."
+ )
category_produk_ids = fields.Many2many('product.public.category', string='Kategori Produk yang Digunakan', domain=lambda self: self._get_default_category_domain())
@api.model
@@ -58,6 +67,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 +75,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 +84,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 +136,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')
@@ -149,6 +161,86 @@ class ResPartner(models.Model):
"this feature", tracking=3)
telegram_id = fields.Char(string="Telegram")
+ # MERCHANT
+ # informasi perusahaan
+ name_merchant = fields.Char(string='Name')
+ pejabat_name = fields.Char(string='Pejabat Name')
+ pic_merchant = fields.Char(string='PIC Merchant', required=True)
+ pic_position = fields.Char(string='Jabatan PIC')
+ address_merchant = fields.Char(string='Alamat')
+ state_merchant = fields.Many2one('res.country.state', string='State')
+ city_merchant = fields.Many2one('vit.kota', string='Kota')
+ district_merchant = fields.Many2one('vit.kecamatan', string='Kecamatan')
+ subDistrict_merchant = fields.Many2one('vit.kelurahan', string='Kelurahan')
+ zip_merchant = fields.Char(string='Kode Pos')
+ bank_name_merchant = fields.Char(string='Nama Bank')
+ rekening_name_merchant = fields.Char(string='Nama Rekening')
+ account_number_merchant = fields.Char(string='Nomor Rekening Bank')
+ email_company_merchant = fields.Char(string='Email Perusahaan')
+ email_sales_merchant = fields.Char(string='Email Sales')
+ email_finance_merchant = fields.Char(string='Email Finance')
+ phone_merchant = fields.Char(string='No. Telepon Perusahaan')
+ mobile_merchant = fields.Char(string='No. Handphone')
+ bisnis_type = fields.Selection([
+ ('1', 'PT'),
+ ('2', 'CV'),
+ ('3', 'Perorangan'),
+ ])
+ website_merchant = fields.Char(string='Website')
+ category_perusahaan = fields.Selection([
+ ('1', 'Principal (Pemegang merk/Produsen)'),
+ ('2', 'Sole Distributor (Distributor Tunggal)'),
+ ('3', 'Authorized Distributor (Distributor Resmi)'),
+ ('4', 'Importer (Pengimpor Barang)'),
+ ('5', 'Wholesaler (Pedagang Besar)'),
+ ])
+ # Informasi Vendor
+ harga_tayang = fields.Char(string='Harga Tayang (HET)')
+ category_produk_ids_merchant = fields.Many2many(
+ 'product.public.category',
+ string='Kategori Produk Merchant',
+ domain=lambda self: self._get_default_category_domain(),
+ relation='res_partner_category_produk_ids_merchant_rel' # Nama tabel relasi berbeda
+ )
+
+ @api.model
+ def _get_default_category_domain(self):
+ return [('parent_id', '=', False)]
+
+ merk_dagang = fields.Char(string='Merk Dagang')
+ is_pengajuan_tempo = fields.Boolean(string='Apakah anda memiliki Form Pengajuan Tempo?')
+ tempo_duration_merchant = fields.Many2one('account.payment.term', string='Durasi Tempo')
+ kredit_limit = fields.Char(string='Kredit Limit')
+ waktu_pengiriman = fields.Char(string='Waktu Pengiriman')
+ terhitung_sejak = fields.Selection([
+ ('1', 'Terima PO'),
+ ('2', 'Barang Dikirimkan'),
+ ('3', 'Tukar Faktur'),
+ ])
+
+ # syarat dagang
+ is_kembali_barang = fields.Char(string='Syarat Pengembalian Barang')
+ tenggat_waktu = fields.Char(string='Tenggat Waktu Perubahan Harga')
+ sertifikat_produk = fields.Char(string='Dokumen/Sertifikat yang Dimiliki Oleh Brand')
+ custom_sertifikat_produk = fields.Char(string='Dokumen/Sertifikat Lainnya')
+ tempo_garansi = fields.Selection([
+ ('1', '6 Bulan Garansi'),
+ ('2', '1 Tahun Garansi'),
+ ('3', '2 Tahun Garansi'),
+ ])
+ explain_garansi = fields.Char(string='Garansi Yang Dimaksudkan')
+ is_order_quantity = fields.Char(string='Apakah Memiliki Minimum Order Quantity (MOQ)')
+
+ # dokumen
+ file_npwp = fields.Many2one('ir.attachment', string="NPWP Perusahaan", tracking=3)
+ file_sppkp = fields.Many2one('ir.attachment', string="SPPKP Perusahaan", tracking=3)
+ file_dokumenKtpDirut = fields.Many2one('ir.attachment', string="KTP Dirut/Direktur", tracking=3)
+ file_kartuNama = fields.Many2one('ir.attachment', string="Kartu Nama", tracking=3)
+ file_suratPernyataan = fields.Many2one('ir.attachment', string="Surat Pernyataan Nomor Rekening", tracking=3)
+ file_fotoKantor = fields.Many2one('ir.attachment', string="Foto Gudang / Kantor Bagian Depan", tracking=3)
+ file_dataProduk = fields.Many2one('ir.attachment', string="Data Produk (Item Name, Gambar, Deskripsi)", tracking=3)
+ file_pricelist = fields.Many2one('ir.attachment', string="Pricelist", tracking=3)
+
@api.model
def _default_payment_term(self):
return self.env.ref('__export__.account_payment_term_26_484409e2').id
@@ -188,15 +280,41 @@ 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:
raise ValidationError(f"Nama '{record.name}' sudah digunakan oleh partner lain!")
+ @api.constrains('npwp')
+ def _check_npwp(self):
+ for record in self:
+ npwp = record.npwp.strip() if record.npwp else ''
+ # Abaikan validasi jika NPWP kosong atau diisi "0"
+ if not npwp or npwp == '0' or npwp == '00.000.000.0-000.000':
+ continue
+
+ # Validasi untuk NPWP 15 digit (format: 99.999.999.9-999.999)
+ if len(npwp) == 20:
+ # Regex untuk 15 digit dengan format titik dan tanda hubung
+ pattern_15_digit = r'^\d{2}\.\d{3}\.\d{3}\.\d{1}-\d{3}\.\d{3}$'
+ if not re.match(pattern_15_digit, npwp):
+ raise ValidationError("Format NPWP 15 digit yang dimasukkan salah. Pastikan format yang benar adalah: 99.999.999.9-999.999")
+
+ # Validasi untuk NPWP 16 digit (hanya angka tanpa titik atau tanda hubung)
+ elif len(npwp) == 16:
+ pattern_16_digit = r'^\d{16}$'
+ if not re.match(pattern_16_digit, npwp):
+ raise ValidationError("Format NPWP 16 digit yang dimasukkan salah. Format yang benar adalah 16 digit angka tanpa titik atau tanda hubung.")
+
+ # Validasi panjang NPWP jika lebih atau kurang dari 15 atau 16 digit
+ else:
+ raise ValidationError("Digit NPWP yang dimasukkan tidak sesuai. Pastikan NPWP memiliki 15 digit dengan format tertentu (99.999.999.9-999.999) atau 16 digit tanpa tanda hubung.")
+
+
def write(self, vals):
# Fungsi rekursif untuk meng-update semua child, termasuk child dari child
def update_children_recursively(partner, vals_for_child):
@@ -254,6 +372,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 +380,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 +389,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)
@@ -288,6 +409,60 @@ class ResPartner(models.Model):
vals['dokumen_foto_kantor'] = vals.get('dokumen_foto_kantor', self.dokumen_foto_kantor)
vals['dokumen_tempat_bekerja'] = vals.get('dokumen_tempat_bekerja', self.dokumen_tempat_bekerja)
+ # MERCHANT
+ # Informasi Perusahaan
+ vals['name_merchant'] = vals.get('name_merchant', self.name_merchant)
+ vals['pejabat_name'] = vals.get('pejabat_name', self.pejabat_name)
+ vals['pic_merchant'] = vals.get('pic_merchant', self.pic_merchant)
+ vals['pic_position'] = vals.get('pic_position', self.pic_position)
+ vals['address_merchant'] = vals.get('address_merchant', self.address_merchant)
+ vals['state_merchant'] = vals.get('state_merchant', self.state_merchant)
+ vals['city_merchant'] = vals.get('city_merchant', self.city_merchant)
+ vals['district_merchant'] = vals.get('district_merchant', self.district_merchant)
+ vals['subDistrict_merchant'] = vals.get('subDistrict_merchant', self.subDistrict_merchant)
+ vals['zip_merchant'] = vals.get('zip_merchant', self.zip_merchant)
+ vals['bank_name_merchant'] = vals.get('bank_name_merchant', self.bank_name_merchant)
+ vals['rekening_name_merchant'] = vals.get('rekening_name_merchant', self.rekening_name_merchant)
+ vals['account_number_merchant'] = vals.get('account_number_merchant', self.account_number_merchant)
+ vals['email_company_merchant'] = vals.get('email_company_merchant', self.email_company_merchant)
+ vals['email_sales_merchant'] = vals.get('email_sales_merchant', self.email_sales_merchant)
+ vals['email_finance_merchant'] = vals.get('email_finance_merchant', self.email_finance_merchant)
+ vals['phone_merchant'] = vals.get('phone_merchant', self.phone_merchant)
+ vals['mobile_merchant'] = vals.get('mobile_merchant', self.mobile_merchant)
+ vals['bisnis_type'] = vals.get('bisnis_type', self.bisnis_type)
+ vals['website_merchant'] = vals.get('website_merchant', self.website_merchant)
+ vals['category_perusahaan'] = vals.get('category_perusahaan', self.category_perusahaan)
+
+ # Informasi Vendor
+ vals['harga_tayang'] = vals.get('harga_tayang', self.harga_tayang)
+ vals['category_produk_ids_merchant'] = vals.get('category_produk_ids_merchant', self.category_produk_ids_merchant)
+ vals['merk_dagang'] = vals.get('merk_dagang', self.merk_dagang)
+ vals['is_pengajuan_tempo'] = vals.get('is_pengajuan_tempo', self.is_pengajuan_tempo)
+ vals['tempo_duration_merchant'] = vals.get('tempo_duration_merchant', self.tempo_duration_merchant)
+ vals['kredit_limit'] = vals.get('kredit_limit', self.kredit_limit)
+ vals['waktu_pengiriman'] = vals.get('waktu_pengiriman', self.waktu_pengiriman)
+ vals['terhitung_sejak'] = vals.get('terhitung_sejak', self.terhitung_sejak)
+
+ # Syarat Dagang
+ vals['is_kembali_barang'] = vals.get('is_kembali_barang', self.is_kembali_barang)
+ vals['tenggat_waktu'] = vals.get('tenggat_waktu', self.tenggat_waktu)
+ vals['sertifikat_produk'] = vals.get('sertifikat_produk', self.sertifikat_produk)
+ vals['custom_sertifikat_produk'] = vals.get('custom_sertifikat_produk', self.custom_sertifikat_produk)
+ vals['tempo_garansi'] = vals.get('tempo_garansi', self.tempo_garansi)
+ vals['explain_garansi'] = vals.get('explain_garansi', self.explain_garansi)
+ vals['is_order_quantity'] = vals.get('is_order_quantity', self.is_order_quantity)
+
+ # Dokumen
+ vals['file_dokumenKtpDirut'] = vals.get('file_dokumenKtpDirut', self.file_dokumenKtpDirut)
+ vals['file_kartuNama'] = vals.get('file_kartuNama', self.file_kartuNama)
+ vals['file_npwp'] = vals.get('file_npwp', self.file_npwp)
+ vals['file_sppkp'] = vals.get('file_sppkp', self.file_sppkp)
+ vals['file_suratPernyataan'] = vals.get('file_suratPernyataan', self.file_suratPernyataan)
+ vals['file_fotoKantor'] = vals.get('file_fotoKantor', self.file_fotoKantor)
+ vals['file_dataProduk'] = vals.get('file_dataProduk', self.file_dataProduk)
+ vals['file_pricelist'] = vals.get('file_pricelist', self.file_pricelist)
+ vals['description'] = vals.get('description', self.description)
+
# Simpan hanya field yang perlu di-update pada child
vals_for_child = {
'customer_type': vals.get('customer_type'),
@@ -323,6 +498,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 +506,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 +515,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'),
@@ -356,7 +534,55 @@ class ResPartner(models.Model):
'dokumen_tempat_bekerja': vals.get('dokumen_tempat_bekerja'),
# internal_notes
- 'comment': vals.get('comment')
+ 'comment': vals.get('comment'),
+
+ # Merchant
+ 'name_merchant': vals.get('name_merchant'),
+ 'pejabat_name': vals.get('pejabat_name'),
+ 'pic_merchant': vals.get('pic_merchant'),
+ 'pic_position': vals.get('pic_position'),
+ 'address_merchant': vals.get('address_merchant'),
+ 'state_merchant': vals.get('state_merchant'),
+ 'city_merchant': vals.get('city_merchant'),
+ 'district_merchant': vals.get('district_merchant'),
+ 'subDistrict_merchant': vals.get('subDistrict_merchant'),
+ 'zip_merchant': vals.get('zip_merchant'),
+ 'bank_name_merchant': vals.get('bank_name_merchant'),
+ 'rekening_name_merchant': vals.get('rekening_name_merchant'),
+ 'account_number_merchant': vals.get('account_number_merchant'),
+ 'email_company_merchant': vals.get('email_company_merchant'),
+ 'email_sales_merchant': vals.get('email_sales_merchant'),
+ 'email_finance_merchant': vals.get('email_finance_merchant'),
+ 'phone_merchant': vals.get('phone_merchant'),
+ 'mobile_merchant': vals.get('mobile_merchant'),
+ 'bisnis_type': vals.get('bisnis_type'),
+ 'website_merchant': vals.get('website_merchant'),
+ 'category_perusahaan': vals.get('category_perusahaan'),
+ 'harga_tayang': vals.get('harga_tayang'),
+ 'category_produk_ids_merchant': vals.get('category_produk_ids_merchant'),
+ 'merk_dagang': vals.get('merk_dagang'),
+ 'is_pengajuan_tempo': vals.get('is_pengajuan_tempo'),
+ 'tempo_duration_merchant': vals.get('tempo_duration_merchant'),
+ 'kredit_limit': vals.get('kredit_limit'),
+ 'waktu_pengiriman': vals.get('waktu_pengiriman'),
+ 'terhitung_sejak': vals.get('terhitung_sejak'),
+ 'is_kembali_barang': vals.get('is_kembali_barang'),
+ 'tenggat_waktu': vals.get('tenggat_waktu'),
+ 'sertifikat_produk': vals.get('sertifikat_produk'),
+ 'custom_sertifikat_produk': vals.get('custom_sertifikat_produk'),
+ 'tempo_garansi': vals.get('tempo_garansi'),
+ 'explain_garansi': vals.get('explain_garansi'),
+ 'is_order_quantity': vals.get('is_order_quantity'),
+
+ 'file_dokumenKtpDirut': vals.get('file_dokumenKtpDirut'),
+ 'file_kartuNama': vals.get('file_kartuNama'),
+ 'file_npwp': vals.get('file_npwp'),
+ 'file_sppkp': vals.get('file_sppkp'),
+ 'file_suratPernyataan': vals.get('file_suratPernyataan'),
+ 'file_fotoKantor': vals.get('file_fotoKantor'),
+ 'file_dataProduk': vals.get('file_dataProduk'),
+ 'file_pricelist': vals.get('file_pricelist'),
+ 'description': vals.get('description'),
}
# Lakukan update pada semua child secara rekursif
@@ -456,6 +682,8 @@ class ResPartner(models.Model):
def _onchange_customer_type(self):
if self.customer_type == 'nonpkp':
self.npwp = '00.000.000.0-000.000'
+ elif self.customer_type == 'pkp':
+ self.npwp = '00.000.000.0-000.000'
def get_check_payment_term(self):
self.ensure_one()
@@ -470,4 +698,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..6ccb6fde 100755
--- a/indoteknik_custom/models/sale_order.py
+++ b/indoteknik_custom/models/sale_order.py
@@ -1,3 +1,5 @@
+from re import search
+
from odoo import fields, models, api, _
from odoo.exceptions import UserError, ValidationError
from datetime import datetime, timedelta
@@ -51,16 +53,89 @@ 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 SaleOrderLine(models.Model):
+ _inherit = 'sale.order.line'
+
+ def unlink(self):
+ lines_to_reject = []
+ for line in self:
+ if line.order_id:
+ now = fields.Datetime.now()
+
+ initial_reason="Product Rejected"
+
+ # Buat lognote untuk product yang di delete
+ log_note = (f"<li>Product '{line.product_id.name}' rejected. </li>"
+ f"<li>Quantity: {line.product_uom_qty}, </li>"
+ f"<li>Date: {now.strftime('%d-%m-%Y')}, </li>"
+ f"<li>Time: {now.strftime('%H:%M:%S')} </li>"
+ f"<li>Reason reject: {initial_reason} </li>")
+
+ lines_to_reject.append({
+ 'sale_order_id': line.order_id.id,
+ 'product_id': line.product_id.id,
+ 'qty_reject': line.product_uom_qty,
+ 'reason_reject': initial_reason, # pesan reason reject
+ 'message_body': log_note,
+ 'order_id': line.order_id,
+ })
+
+ # Call the original unlink method
+ result = super(SaleOrderLine, self).unlink()
+
+ # After deletion, create reject lines and post messages
+ SalesOrderReject = self.env['sales.order.reject']
+ for reject_data in lines_to_reject:
+ # Buat line baru di reject line
+ SalesOrderReject.create({
+ 'sale_order_id': reject_data['sale_order_id'],
+ 'product_id': reject_data['product_id'],
+ 'qty_reject': reject_data['qty_reject'],
+ 'reason_reject': reject_data['reason_reject'],
+ })
+
+ # Post to chatter with a more prominent message
+ reject_data['order_id'].message_post(
+ body=reject_data['message_body'],
+ author_id=self.env.user.partner_id.id, # menampilkan pesan di lognote sebagai current user
+ )
+
+ return result
class SaleOrder(models.Model):
_inherit = "sale.order"
+ ongkir_ke_xpdc = fields.Float(string='Ongkir ke Ekspedisi', help='Biaya ongkir ekspedisi', copy=False, index=True, tracking=3)
+
+ metode_kirim_ke_xpdc = fields.Selection([
+ ('indoteknik_deliv', 'Indoteknik Delivery'),
+ ('lalamove', 'Lalamove'),
+ ('grab', 'Grab'),
+ ('gojek', 'Gojek'),
+ ('deliveree', 'Deliveree'),
+ ('other', 'Other'),
+ ], string='Metode Kirim Ke Ekspedisi', copy=False, index=True, tracking=3)
+
+ koli_lines = fields.One2many('sales.order.koli', 'sale_order_id', string='Sales Order Koli', auto_join=True)
fulfillment_line_v2 = fields.One2many('sales.order.fulfillment.v2', 'sale_order_id', string='Fullfillment2')
fullfillment_line = fields.One2many('sales.order.fullfillment', 'sales_order_id', string='Fullfillment')
reject_line = fields.One2many('sales.order.reject', 'sale_order_id', string='Reject Lines')
order_sales_match_line = fields.One2many('sales.order.purchase.match', 'sales_order_id', string='Purchase Match Lines', states={'cancel': [('readonly', True)], 'done': [('readonly', True)]}, copy=True)
total_margin = fields.Float('Total Margin', compute='_compute_total_margin', help="Total Margin in Sales Order Header")
+ total_before_margin = fields.Float('Total Before Margin', compute='_compute_total_before_margin', help="Total Margin in Sales Order Header")
total_percent_margin = fields.Float('Total Percent Margin', compute='_compute_total_percent_margin', help="Total % Margin in Sales Order Header")
+ total_margin_excl_third_party = fields.Float('Before Margin', help="Before Margin in Sales Order Header", compute='_compute_total_margin_excl_third_party')
approval_status = fields.Selection([
('pengajuan1', 'Approval Manager'),
('pengajuan2', 'Approval Pimpinan'),
@@ -92,6 +167,7 @@ class SaleOrder(models.Model):
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]",
help="Dipakai untuk alamat tempel", tracking=True)
fee_third_party = fields.Float('Fee Pihak Ketiga')
+ biaya_lain_lain = fields.Float('Biaya Lain Lain')
so_status = fields.Selection([
('terproses', 'Terproses'),
('sebagian', 'Sebagian Diproses'),
@@ -127,9 +203,9 @@ class SaleOrder(models.Model):
customer_type = fields.Selection([
('pkp', 'PKP'),
('nonpkp', 'Non PKP')
- ], required=True)
- sppkp = fields.Char(string="SPPKP", required=True, tracking=True)
- npwp = fields.Char(string="NPWP", required=True, tracking=True)
+ ], required=True, compute='_compute_partner_field')
+ sppkp = fields.Char(string="SPPKP", required=True, tracking=True, compute='_compute_partner_field')
+ npwp = fields.Char(string="NPWP", required=True, tracking=True, compute='_compute_partner_field')
purchase_total = fields.Monetary(string='Purchase Total', compute='_compute_purchase_total')
voucher_id = fields.Many2one(comodel_name='voucher', string='Voucher', copy=False)
applied_voucher_id = fields.Many2one(comodel_name='voucher', string='Applied Voucher', copy=False)
@@ -137,11 +213,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 +266,16 @@ 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
+ )
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 +289,96 @@ 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)]")
+ hold_outgoing = fields.Boolean('Hold Outgoing SO', tracking=3)
+ state_ask_cancel = fields.Selection([
+ ('hold', 'Hold'),
+ ('approve', 'Approve')
+ ], tracking=True, string='State Cancel', copy=False)
+
+ ready_to_ship_status_detail = fields.Char(
+ string='Status Shipping Detail',
+ compute='_compute_ready_to_ship_status_detail'
+ )
+
+
+ def _compute_total_margin_excl_third_party(self):
+ for order in self:
+ if order.amount_untaxed == 0:
+ order.total_margin_excl_third_party = 0
+ continue
+
+ # order.total_percent_margin = round((order.total_margin / (order.amount_untaxed-delivery_amt-order.fee_third_party)) * 100, 2)
+ order.total_margin_excl_third_party = round((order.total_before_margin / (order.amount_untaxed)) * 100, 2)
+ # order.total_percent_margin = round((order.total_margin / (order.amount_untaxed)) * 100, 2)
+
+ def ask_retur_cancel_purchasing(self):
+ for rec in self:
+ if self.env.user.has_group('indoteknik_custom.group_role_purchasing'):
+ rec.state_ask_cancel = 'approve'
+ return {
+ 'type': 'ir.actions.client',
+ 'tag': 'display_notification',
+ 'params': {
+ 'title': 'Persetujuan Diberikan',
+ 'message': 'Proses cancel sudah disetujui',
+ 'type': 'success',
+ 'sticky': True
+ }
+ }
+ else:
+ rec.state_ask_cancel = 'hold'
+ return {
+ 'type': 'ir.actions.client',
+ 'tag': 'display_notification',
+ 'params': {
+ 'title': 'Menunggu Persetujuan',
+ 'message': 'Tim Purchasing akan memproses permintaan Anda',
+ 'type': 'warning',
+ 'sticky': False
+ }
+ }
+
+ def hold_unhold_qty_outgoing_so(self):
+ if self.hold_outgoing == True:
+ self.hold_outgoing = False
+ else:
+ self.hold_outgoing = True
+
+ def _validate_uniform_taxes(self):
+ for order in self:
+ tax_sets = set()
+ for line in order.order_line:
+ tax_ids = tuple(sorted(line.tax_id.ids))
+ if tax_ids:
+ tax_sets.add(tax_ids)
+ if len(tax_sets) > 1:
+ raise ValidationError("Semua produk dalam Sales Order harus memiliki kombinasi pajak yang sama.")
+
+ # @api.constrains('fee_third_party', 'delivery_amt', 'biaya_lain_lain')
+ # def _check_total_margin_excl_third_party(self):
+ # for rec in self:
+ # if rec.fee_third_party == 0 and rec.total_margin_excl_third_party != rec.total_percent_margin:
+ # # Gunakan direct SQL atau flag context untuk menghindari rekursi
+ # self.env.cr.execute("""
+ # UPDATE sale_order
+ # SET total_margin_excl_third_party = %s
+ # WHERE id = %s
+ # """, (rec.total_percent_margin, rec.id))
+ # self.invalidate_cache()
+
+ @api.constrains('shipping_option_id')
+ def _check_shipping_option(self):
+ for rec in self:
+ if rec.shipping_option_id:
+ rec.delivery_amt = rec.shipping_option_id.price
def _compute_shipping_method_picking(self):
for order in self:
@@ -255,15 +426,36 @@ 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
+ self.message_post(
+ body=(
+ f"<b>Estimasi pengiriman Indoteknik berhasil:</b><br/>"
+ f"Layanan: {shipping_option.name}<br/>"
+ f"ETD: {shipping_option.etd}<br/>"
+ f"Biaya: Rp {shipping_option.price:,}<br/>"
+ f"Provider: {shipping_option.provider}"
+ ),
+ message_type="comment",
+ )
+
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,35 +473,50 @@ 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()
+
+ _logger.info(f"Shipping options: {shipping_options}")
+
+ 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
+
+ _logger.info(f"Shipping option SO ID: {self.shipping_option_id}")
+
+ self.message_post(
+ body=f"Estimasi Ongkos Kirim: Rp{self.delivery_amt}<br/>Detail Lain:<br/>"
+ f"{'<br/>'.join([f'Service: {s[0]}, Description: {s[1]}, ETD: {s[2]} hari, Cost: Rp {s[3]}' for s in shipping_options])}",
+ message_type="comment"
+ )
+
+ # 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])}", message_type="comment")
- 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}")
else:
raise UserError("Gagal mendapatkan estimasi ongkir.")
+
def _call_rajaongkir_api(self, total_weight, destination_subsdistrict_id):
url = 'https://pro.rajaongkir.com/api/cost'
@@ -409,11 +616,11 @@ class SaleOrder(models.Model):
delivery_amt = order.delivery_amt
else:
delivery_amt = 0
- order.percent_margin_after_delivery_purchase = round((order.margin_after_delivery_purchase / (order.amount_untaxed-delivery_amt-order.fee_third_party)) * 100, 2)
+ order.percent_margin_after_delivery_purchase = round((order.margin_after_delivery_purchase / (order.amount_untaxed-delivery_amt-order.fee_third_party-order.biaya_lain_lain)) * 100, 2)
def _compute_date_kirim(self):
for rec in self:
- picking = self.env['stock.picking'].search([('sale_id', '=', rec.id), ('state', 'not in', ['cancel'])], order='date_doc_kirim desc', limit=1)
+ picking = self.env['stock.picking'].search([('sale_id', '=', rec.id), ('state', 'not in', ['cancel']), ('name', 'not ilike', 'BU/PICK/%')], order='date_doc_kirim desc', limit=1)
rec.date_kirim_ril = picking.date_doc_kirim
rec.date_status_done = picking.date_done
rec.date_driver_arrival = picking.driver_arrival_date
@@ -435,19 +642,131 @@ 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
+ current_date = datetime.now().date()
+ 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 = current_date + timedelta(days=rec.estimated_arrival_days)
+ rec.eta_date_start = current_date + 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):
+ 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}
+
+ def _calculate_etrts_date(self):
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
+ if not rec.date_order:
+ rec.expected_ready_to_ship = False
+ return
+
+ current_date = datetime.now().date()
+
+ max_slatime = 1 # Default SLA jika tidak ada
+ slatime = self.calculate_sla_by_vendor(rec.order_line)
+ max_slatime = max(max_slatime, slatime['slatime'])
+
+ sum_days = max_slatime + self.get_days_until_next_business_day(current_date) - 1
+ if not rec.estimated_arrival_days:
+ rec.estimated_arrival_days = sum_days
+
+ eta_date = current_date + timedelta(days=sum_days)
+ rec.commitment_date = eta_date
+ rec.expected_ready_to_ship = eta_date
+
+ @api.depends("order_line.product_id", "date_order")
+ def _compute_etrts_date(self): #Function to calculate Estimated Ready To Ship Date
+ self._calculate_etrts_date()
+
+
+ def _validate_expected_ready_ship_date(self):
+ for rec in self:
+ if rec.expected_ready_to_ship and rec.commitment_date:
+ current_date = datetime.now().date()
+ # Hanya membandingkan tanggal saja, tanpa jam
+ expected_date = rec.expected_ready_to_ship.date()
+
+ max_slatime = 1 # Default SLA jika tidak ada
+ slatime = self.calculate_sla_by_vendor(rec.order_line)
+ max_slatime = max(max_slatime, slatime['slatime'])
+ sum_days = max_slatime + self.get_days_until_next_business_day(current_date) - 1
+ eta_minimum = current_date + timedelta(days=sum_days)
+
+ if expected_date < eta_minimum:
+ rec.expected_ready_to_ship = eta_minimum
+ raise ValidationError(
+ "Tanggal 'Expected Ready to Ship' tidak boleh lebih kecil dari {}. Mohon pilih tanggal minimal {}."
+ .format(eta_minimum.strftime('%d-%m-%Y'), eta_minimum.strftime('%d-%m-%Y'))
+ )
+ else:
+ rec.commitment_date = rec.expected_ready_to_ship
+
+
+ @api.onchange('expected_ready_to_ship') #Hangle Onchange form Expected Ready to Ship
+ def _onchange_expected_ready_ship_date(self):
+ self._validate_expected_ready_ship_date()
+
+ 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):
"""
@@ -498,6 +817,33 @@ class SaleOrder(models.Model):
if self.email and not re.match(pattern, self.email):
raise UserError('Email yang anda input kurang valid')
+ # @api.constrains('delivery_amt', 'carrier_id', 'shipping_cost_covered')
+ def _validate_delivery_amt(self):
+ is_indoteknik = self.carrier_id.id == 1 or self.shipping_cost_covered == 'indoteknik'
+ is_active_id = not self.env.context.get('active_id', [])
+
+ if is_indoteknik and is_active_id:
+ if self.delivery_amt == 0:
+ if self.carrier_id.id == 1:
+ raise UserError('Untuk Kurir Indoteknik Delivery, estimasi ongkos kirim belum diisi.')
+ else:
+ raise UserError('Untuk Shipping Covered Indoteknik, estimasi ongkos kirim belum diisi.')
+
+ if self.delivery_amt < 100:
+ if self.carrier_id.id == 1:
+ raise UserError('Untuk Kurir Indoteknik Delivery, estimasi ongkos kirim belum memenuhi tarif minimum.')
+ else:
+ raise UserError('Untuk Shipping Covered Indoteknik, estimasi ongkos kirim belum memenuhi tarif minimum.')
+
+
+ # if self.delivery_amt < 5000:
+ # if (self.carrier_id.id == 1 or self.shipping_cost_covered == 'indoteknik') and not self.env.context.get('active_id', []):
+ # if self.carrier_id.id == 1:
+ # raise UserError('Untuk Kurir Indoteknik Delivery, estimasi ongkos kirim belum memenuhi jumlah minimum.')
+ # else:
+ # raise UserError('Untuk Shipping Covered Indoteknik, estimasi ongkos kirim belum memenuhi jumlah minimum.')
+
+
def override_allow_create_invoice(self):
if not self.env.user.is_accounting:
raise UserError('Hanya Finance Accounting yang dapat klik tombol ini')
@@ -642,17 +988,14 @@ 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)
-
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):
so_state = ['sale']
sales = self.search([
@@ -706,6 +1049,13 @@ class SaleOrder(models.Model):
# return [('id', 'not in', order_ids)]
# return ['&', ('order_line.invoice_lines.move_id.move_type', 'in', ('out_invoice', 'out_refund')), ('order_line.invoice_lines.move_id', operator, value)]
+ @api.depends('partner_id')
+ def _compute_partner_field(self):
+ for order in self:
+ partner = order.partner_id.parent_id or order.partner_id
+ order.npwp = partner.npwp
+ order.sppkp = partner.sppkp
+ order.customer_type = partner.customer_type
@api.onchange('partner_id')
def onchange_partner_contact(self):
@@ -807,6 +1157,7 @@ class SaleOrder(models.Model):
self._validate_order()
for order in self:
+ order._validate_uniform_taxes()
order.order_line.validate_line()
term_days = 0
@@ -828,8 +1179,29 @@ class SaleOrder(models.Model):
raise UserError("Customer Reference kosong, di isi dengan NO PO jika PO tidak ada mohon ditulis Tanpa PO")
if not order.user_id.active:
raise UserError("Salesperson sudah tidak aktif, mohon diisi yang benar pada data SO dan Contact")
-
+
+ def check_product_bom(self):
+ for order in self:
+ for line in order.order_line:
+ if 'bom-it' in line.name.lower() or 'bom' in line.product_id.default_code.lower() if line.product_id.default_code else False:
+ search_bom = self.env['mrp.production'].search([('product_id', '=', line.product_id.id)],order='name desc')
+ if search_bom:
+ confirmed_bom = search_bom.filtered(lambda x: x.state == 'confirmed')
+ if not confirmed_bom:
+ raise UserError("Product BOM belum dikonfirmasi di Manufacturing Orders. Silakan hubungi MD.")
+ else:
+ raise UserError("Product BOM tidak di temukan di manufacturing orders, silahkan hubungi MD")
+
+ def check_duplicate_product(self):
+ for order in self:
+ for line in order.order_line:
+ search_product = self.env['sale.order.line'].search([('product_id', '=', line.product_id.id), ('order_id', '=', order.id)])
+ if len(search_product) > 1:
+ raise UserError("Terdapat DUPLIKASI data pada Product {}".format(line.product_id.display_name))
+
def sale_order_approve(self):
+ self.check_duplicate_product()
+ self.check_product_bom()
self.check_credit_limit()
self.check_limit_so_to_invoice()
if self.validate_different_vendor() and not self.vendor_approval:
@@ -838,6 +1210,7 @@ class SaleOrder(models.Model):
self._validate_order()
for order in self:
+ order._validate_uniform_taxes()
order.order_line.validate_line()
order.check_data_real_delivery_address()
order._validate_order()
@@ -889,6 +1262,7 @@ class SaleOrder(models.Model):
order.approval_status = 'pengajuan2'
return self._create_approval_notification('Pimpinan')
elif order._requires_approval_margin_manager():
+ self.check_product_bom()
self.check_credit_limit()
self.check_limit_so_to_invoice()
order.approval_status = 'pengajuan1'
@@ -1068,6 +1442,9 @@ class SaleOrder(models.Model):
def action_confirm(self):
for order in self:
+ order._validate_uniform_taxes()
+ order.check_duplicate_product()
+ order.check_product_bom()
order.check_credit_limit()
order.check_limit_so_to_invoice()
if self.validate_different_vendor() and not self.vendor_approval:
@@ -1108,6 +1485,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()
@@ -1125,6 +1503,8 @@ class SaleOrder(models.Model):
def action_cancel(self):
# TODO stephan prevent cancel if have invoice, do, and po
+ if self.state_ask_cancel != 'approve' and self.state not in ['draft', 'sent']:
+ raise UserError("Anda harus approval purchasing terlebih dahulu")
main_parent = self.partner_id.get_main_parent()
if self._name != 'sale.order':
return super(SaleOrder, self).action_cancel()
@@ -1198,10 +1578,11 @@ class SaleOrder(models.Model):
return False
def _requires_approval_margin_leader(self):
- return self.total_percent_margin < 15 and not self.env.user.is_leader
+ return self.total_percent_margin <= 15 and not self.env.user.is_leader
def _requires_approval_margin_manager(self):
- return self.total_percent_margin >= 15 and not self.env.user.is_leader and not self.env.user.is_sales_manager
+ return 15 < self.total_percent_margin <= 24 and not self.env.user.is_sales_manager and not self.env.user.is_leader
+ # return self.total_percent_margin >= 15 and not self.env.user.is_leader and not self.env.user.is_sales_manager
def _create_approval_notification(self, approval_role):
title = 'Warning'
@@ -1238,8 +1619,16 @@ class SaleOrder(models.Model):
def _compute_total_margin(self):
for order in self:
total_margin = sum(line.item_margin for line in order.order_line if line.product_id)
+ if order.ongkir_ke_xpdc:
+ total_margin -= order.ongkir_ke_xpdc
+
order.total_margin = total_margin
+ def _compute_total_before_margin(self):
+ for order in self:
+ total_before_margin = sum(line.item_before_margin for line in order.order_line if line.product_id)
+ order.total_before_margin = total_before_margin
+
def _compute_total_percent_margin(self):
for order in self:
if order.amount_untaxed == 0:
@@ -1249,7 +1638,9 @@ class SaleOrder(models.Model):
delivery_amt = order.delivery_amt
else:
delivery_amt = 0
- order.total_percent_margin = round((order.total_margin / (order.amount_untaxed-delivery_amt-order.fee_third_party)) * 100, 2)
+
+ # order.total_percent_margin = round((order.total_margin / (order.amount_untaxed-delivery_amt-order.fee_third_party)) * 100, 2)
+ order.total_percent_margin = round((order.total_margin / (order.amount_untaxed-order.fee_third_party-order.biaya_lain_lain)) * 100, 2)
# order.total_percent_margin = round((order.total_margin / (order.amount_untaxed)) * 100, 2)
@api.onchange('sales_tax_id')
@@ -1503,18 +1894,22 @@ 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._validate_expected_ready_ship_date()
+ order._validate_delivery_amt()
+ # order._check_total_margin_excl_third_party()
# order._update_partner_details()
return order
- def write(self, vals):
+ # def write(self, vals):
# Call the super method to handle the write operation
- res = super(SaleOrder, self).write(vals)
-
+ # 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()
- return res
+ # return res
def _update_partner_details(self):
for order in self:
@@ -1543,5 +1938,46 @@ class SaleOrder(models.Model):
if command[0] == 0: # A new line is being added
raise UserError(
"SO tidak dapat ditambahkan produk baru karena SO sudah menjadi sale order.")
+
res = super(SaleOrder, self).write(vals)
- return res \ No newline at end of file
+ # self._check_total_margin_excl_third_party()
+ if any(fields in vals for fields in ['delivery_amt', 'carrier_id', 'shipping_cost_covered']):
+ self._validate_delivery_amt()
+ if any(field in vals for field in ["order_line", "client_order_ref"]):
+ self._calculate_etrts_date()
+ return res
+
+ # @api.depends('commitment_date')
+ def _compute_ready_to_ship_status_detail(self):
+ for order in self:
+ eta = order.commitment_date
+
+ match_lines = self.env['purchase.order.sales.match'].search([
+ ('sale_id', '=', order.id)
+ ])
+
+ if match_lines:
+ for match in match_lines:
+ po = match.purchase_order_id
+ product = match.product_id
+
+ po_line = self.env['purchase.order.line'].search([
+ ('order_id', '=', po.id),
+ ('product_id', '=', product.id)
+ ], limit=1)
+
+ stock_move = self.env['stock.move'].search([
+ ('purchase_line_id', '=', po_line.id)
+ ], limit=1)
+ picking_in = stock_move.picking_id
+
+ result_date = picking_in.date_done if picking_in else None
+ if result_date:
+ status = "Early" if result_date < eta else "Delay"
+ result_date_str = result_date.strftime('%m/%d/%Y')
+ eta_str = eta.strftime('%m/%d/%Y')
+ order.ready_to_ship_status_detail = f"Expected: {eta_str} | Realtime: {result_date_str} | {status}"
+ else:
+ order.ready_to_ship_status_detail = "On Track"
+ else:
+ order.ready_to_ship_status_detail = 'On Track' \ No newline at end of file
diff --git a/indoteknik_custom/models/sale_order_line.py b/indoteknik_custom/models/sale_order_line.py
index aed95aab..2450abd4 100644
--- a/indoteknik_custom/models/sale_order_line.py
+++ b/indoteknik_custom/models/sale_order_line.py
@@ -6,6 +6,7 @@ from datetime import datetime, timedelta
class SaleOrderLine(models.Model):
_inherit = 'sale.order.line'
item_margin = fields.Float('Margin', compute='compute_item_margin', help="Total Margin in Sales Order Header")
+ item_before_margin = fields.Float('Before Margin', compute='compute_item_before_margin', help="Total Margin in Sales Order Header")
item_percent_margin = fields.Float('%Margin', compute='compute_item_margin', help="Total % Margin in Sales Order Header")
initial_discount = fields.Float('Initial Discount')
vendor_id = fields.Many2one(
@@ -146,6 +147,24 @@ class SaleOrderLine(models.Model):
if not line.margin_md:
line.margin_md = line.item_percent_margin
+ def compute_item_before_margin(self):
+ for line in self:
+ if not line.product_id or line.product_id.type == 'service' \
+ or line.price_unit <= 0 or line.product_uom_qty <= 0 \
+ or not line.vendor_id:
+ line.item_before_margin = 0
+ continue
+ # calculate margin without tax
+ sales_price = line.price_reduce_taxexcl * line.product_uom_qty
+
+ purchase_price = line.purchase_price
+ if line.purchase_tax_id.price_include:
+ purchase_price = line.purchase_price / 1.11
+
+ purchase_price = purchase_price * line.product_uom_qty
+ margin_per_item = sales_price - purchase_price
+ line.item_before_margin = margin_per_item
+
@api.onchange('vendor_id')
def onchange_vendor_id(self):
# TODO : need to change this logic @stephan
diff --git a/indoteknik_custom/models/sales_order_koli.py b/indoteknik_custom/models/sales_order_koli.py
new file mode 100644
index 00000000..c782a40e
--- /dev/null
+++ b/indoteknik_custom/models/sales_order_koli.py
@@ -0,0 +1,26 @@
+from odoo import fields, models, api, _
+from odoo.exceptions import AccessError, UserError, ValidationError
+from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
+import logging
+
+_logger = logging.getLogger(__name__)
+
+
+class SalesOrderKoli(models.Model):
+ _name = 'sales.order.koli'
+ _description = 'Sales Order Koli'
+ _order = 'sale_order_id, id'
+ _rec_name = 'koli_id'
+
+ sale_order_id = fields.Many2one(
+ 'sale.order',
+ string='Sale Order Reference',
+ required=True,
+ ondelete='cascade',
+ index=True,
+ copy=False,
+ )
+ koli_id = fields.Many2one('check.koli', string='Koli')
+ picking_id = fields.Many2one('stock.picking', string='Picking')
+ state = fields.Selection([('not_delivered', 'Not Delivered'), ('delivered', 'Delivered')], string='Status', default='not_delivered')
+
diff --git a/indoteknik_custom/models/sales_order_reject.py b/indoteknik_custom/models/sales_order_reject.py
index 9983c64e..b180fad6 100644
--- a/indoteknik_custom/models/sales_order_reject.py
+++ b/indoteknik_custom/models/sales_order_reject.py
@@ -13,3 +13,30 @@ class SalesOrderReject(models.Model):
product_id = fields.Many2one('product.product', string='Product')
qty_reject = fields.Float(string='Qty')
reason_reject = fields.Char(string='Reason Reject')
+
+ def write(self, vals):
+ # Check if reason_reject is being updated
+ if 'reason_reject' in vals:
+ for record in self:
+ old_reason = record.reason_reject
+ new_reason = vals['reason_reject']
+
+ # Only post a message if the reason actually changed
+ if old_reason != new_reason:
+ now = fields.Datetime.now()
+
+ # Create the log note for the updated reason
+ log_note = (f"<li>Product '{record.product_id.name}' rejection reason updated:</li>"
+ f"<li>From: {old_reason}</li>"
+ f"<li>To: {new_reason}</li>"
+ f"<li>Updated on: {now.strftime('%d-%m-%Y')} at {now.strftime('%H:%M:%S')}</li>")
+
+ # Post ke lognote
+ if record.sale_order_id:
+ record.sale_order_id.message_post(
+ body=log_note,
+ author_id=self.env.user.partner_id.id,
+ )
+
+ # Call the original write method
+ return super(SalesOrderReject, self).write(vals) \ No newline at end of file
diff --git a/indoteknik_custom/models/shipment_group.py b/indoteknik_custom/models/shipment_group.py
index df3f1bb4..b7d7ac12 100644
--- a/indoteknik_custom/models/shipment_group.py
+++ b/indoteknik_custom/models/shipment_group.py
@@ -14,6 +14,13 @@ class ShipmentGroup(models.Model):
number = fields.Char(string='Document No', index=True, copy=False, readonly=True, tracking=True)
shipment_line = fields.One2many('shipment.group.line', 'shipment_id', string='Shipment Group Lines', auto_join=True)
partner_id = fields.Many2one('res.partner', string='Customer')
+ carrier_id = fields.Many2one('delivery.carrier', string='Ekspedisi')
+ total_colly_line = fields.Float(string='Total Colly', compute='_compute_total_colly_line')
+
+ @api.depends('shipment_line.total_colly')
+ def _compute_total_colly_line(self):
+ for rec in self:
+ rec.total_colly_line = sum(rec.shipment_line.mapped('total_colly'))
@api.model
def create(self, vals):
@@ -35,6 +42,26 @@ class ShipmentGroupLine(models.Model):
('indoteknik', 'Indoteknik'),
('customer', 'Customer')
], string='Shipping Paid by', copy=False)
+ total_colly = fields.Float(string='Total Colly')
+ carrier_id = fields.Many2one('delivery.carrier', string='Ekspedisi')
+
+ @api.constrains('picking_id')
+ def _check_picking_id(self):
+ for rec in self:
+ if not rec.picking_id:
+ continue
+
+ duplicates = self.env['shipment.group.line'].search([
+ ('picking_id', '=', rec.picking_id.id),
+ ('id', '!=', rec.id)
+ ])
+
+ if duplicates:
+ shipment_numbers = duplicates.mapped('shipment_id.number')
+ raise UserError(
+ f"Picking {rec.picking_id.name} sudah discan dalam shipment group berikut: {', '.join(shipment_numbers)}! "
+ "Satu picking hanya boleh dimasukkan dalam satu shipment group."
+ )
@api.depends('picking_id.state')
def _compute_state(self):
@@ -63,12 +90,20 @@ class ShipmentGroupLine(models.Model):
if self.shipment_id.partner_id and self.shipment_id.partner_id != picking.partner_id:
raise UserError('Partner must be same as shipment group')
+ if self.shipment_id.carrier_id and self.shipment_id.carrier_id != picking.carrier_id:
+ raise UserError('carrier must be same as shipment group')
+
self.partner_id = picking.partner_id
self.shipping_paid_by = picking.sale_id.shipping_paid_by
+ self.carrier_id = picking.carrier_id.id
+ self.total_colly = picking.total_mapping_koli
if not self.shipment_id.partner_id:
self.shipment_id.partner_id = picking.partner_id
+ if not self.shipment_id.carrier_id:
+ self.shipment_id.carrier_id = picking.carrier_id
+
self.sale_id = picking.sale_id
@api.model
diff --git a/indoteknik_custom/models/solr/product_product.py b/indoteknik_custom/models/solr/product_product.py
index 667511b2..d8bc3973 100644
--- a/indoteknik_custom/models/solr/product_product.py
+++ b/indoteknik_custom/models/solr/product_product.py
@@ -57,6 +57,8 @@ class ProductProduct(models.Model):
is_in_bu = True if variant.qty_free_bandengan > 0 else False
document = solr_model.get_doc('variants', variant.id)
+
+ carousel = [ir_attachment.api_image('image.carousel', 'image', carousel.product_id.id) for carousel in variant.product_tmpl_id.image_carousel_lines],
document.update({
'id': variant.id,
@@ -67,10 +69,11 @@ class ProductProduct(models.Model):
'product_id_i': variant.id,
'template_id_i': variant.product_tmpl_id.id,
'image_s': ir_attachment.api_image('product.template', 'image_512', variant.product_tmpl_id.id),
+ 'image_carousel_s': [ir_attachment.api_image('image.carousel', 'image', carousel.id) for carousel in variant.product_tmpl_id.image_carousel_lines],
'image_mobile_s': ir_attachment.api_image('product.template', 'image_256', variant.product_tmpl_id.id),
'stock_total_f': variant.qty_stock_vendor,
'weight_f': variant.weight,
- 'manufacture_id_i': variant.product_tmpl_id.x_manufacture.id or 0,
+ 'manufacture_id_i': variant.product_tmpl_id.x_manufacture.id or 0,
'manufacture_name_s': variant.product_tmpl_id.x_manufacture.x_name or '',
'manufacture_name': variant.product_tmpl_id.x_manufacture.x_name or '',
'image_promotion_1_s': ir_attachment.api_image('x_manufactures', 'image_promotion_1', variant.product_tmpl_id.x_manufacture.id),
diff --git a/indoteknik_custom/models/solr/product_template.py b/indoteknik_custom/models/solr/product_template.py
index 8afff6e3..c4aefe19 100644
--- a/indoteknik_custom/models/solr/product_template.py
+++ b/indoteknik_custom/models/solr/product_template.py
@@ -26,7 +26,7 @@ class ProductTemplate(models.Model):
'function_name': function_name
})
- @api.constrains('name', 'default_code', 'weight', 'x_manufacture', 'public_categ_ids', 'search_rank', 'search_rank_weekly', 'image_1920', 'unpublished')
+ @api.constrains('name', 'default_code', 'weight', 'x_manufacture', 'public_categ_ids', 'search_rank', 'search_rank_weekly', 'image_1920', 'unpublished','image_carousel_lines')
def _create_solr_queue_sync_product_template(self):
self._create_solr_queue('_sync_product_template_to_solr')
@@ -85,6 +85,12 @@ class ProductTemplate(models.Model):
cleaned_desc = BeautifulSoup(template.website_description or '', "html.parser").get_text()
website_description = template.website_description if cleaned_desc else ''
+ # carousel_images = ', '.join([self.env['ir.attachment'].api_image('image.carousel', 'image', carousel.id) for carousel in template.image_carousel_lines])
+ carousel_images = []
+ for carousel in template.image_carousel_lines:
+ image_url = self.env['ir.attachment'].api_image('image.carousel', 'image', carousel.id)
+ if image_url: # Hanya tambahkan jika URL valid
+ carousel_images.append(image_url)
document = solr_model.get_doc('product', template.id)
document.update({
"id": template.id,
@@ -94,6 +100,7 @@ class ProductTemplate(models.Model):
"product_rating_f": template.virtual_rating,
"product_id_i": template.id,
"image_s": self.env['ir.attachment'].api_image('product.template', 'image_512', template.id),
+ "image_carousel_ss": carousel_images if carousel_images else [],
'image_mobile_s': self.env['ir.attachment'].api_image('product.template', 'image_256', template.id),
"variant_total_i": template.product_variant_count,
"stock_total_f": template.qty_stock_vendor,
diff --git a/indoteknik_custom/models/solr/x_banner_banner.py b/indoteknik_custom/models/solr/x_banner_banner.py
index 8452644c..aa6e0c2a 100644
--- a/indoteknik_custom/models/solr/x_banner_banner.py
+++ b/indoteknik_custom/models/solr/x_banner_banner.py
@@ -23,7 +23,7 @@ class XBannerBanner(models.Model):
'function_name': function_name
})
- @api.constrains('x_name', 'x_url_banner', 'background_color', 'x_banner_image', 'x_banner_category', 'x_relasi_manufacture', 'x_sequence_banner', 'x_status_banner', 'sequence', 'group_by_week', 'headline_banner_s', 'description_banner_s')
+ @api.constrains('x_name', 'x_url_banner', 'background_color', 'x_banner_image', 'x_banner_category', 'x_relasi_manufacture', 'x_sequence_banner', 'x_status_banner', 'sequence', 'group_by_week', 'headline_banner_s', 'description_banner_s', 'x_keyword_banner')
def _create_solr_queue_sync_brands(self):
self._create_solr_queue('_sync_banners_to_solr')
@@ -51,6 +51,7 @@ class XBannerBanner(models.Model):
'group_by_week': banners.group_by_week or '',
'headline_banner_s': banners.x_headline_banner or '',
'description_banner_s': banners.x_description_banner or '',
+ 'keyword_banner_s': banners.x_keyword_banner or '',
})
self.solr().add([document])
banners.update_last_update_solr()
diff --git a/indoteknik_custom/models/stock_backorder_confirmation.py b/indoteknik_custom/models/stock_backorder_confirmation.py
new file mode 100644
index 00000000..d8a41f54
--- /dev/null
+++ b/indoteknik_custom/models/stock_backorder_confirmation.py
@@ -0,0 +1,33 @@
+from odoo import models, fields, api
+from odoo.tools.float_utils import float_compare
+
+class StockBackorderConfirmation(models.TransientModel):
+ _inherit = 'stock.backorder.confirmation'
+
+ def process(self):
+ pickings_to_do = self.env['stock.picking']
+ pickings_not_to_do = self.env['stock.picking']
+ for line in self.backorder_confirmation_line_ids:
+ line.picking_id.send_mail_bills()
+ # line.picking_id.send_koli_to_so()
+ if line.to_backorder is True:
+ pickings_to_do |= line.picking_id
+ else:
+ pickings_not_to_do |= line.picking_id
+
+ for pick_id in pickings_not_to_do:
+ moves_to_log = {}
+ for move in pick_id.move_lines:
+ if float_compare(move.product_uom_qty,
+ move.quantity_done,
+ precision_rounding=move.product_uom.rounding) > 0:
+ moves_to_log[move] = (move.quantity_done, move.product_uom_qty)
+ pick_id._log_less_quantities_than_expected(moves_to_log)
+
+ pickings_to_validate = self.env.context.get('button_validate_picking_ids')
+ if pickings_to_validate:
+ pickings_to_validate = self.env['stock.picking'].browse(pickings_to_validate).with_context(skip_backorder=True)
+ if pickings_not_to_do:
+ pickings_to_validate = pickings_to_validate.with_context(picking_ids_not_to_backorder=pickings_not_to_do.ids)
+ return pickings_to_validate.button_validate()
+ return True
diff --git a/indoteknik_custom/models/stock_immediate_transfer.py b/indoteknik_custom/models/stock_immediate_transfer.py
index 21210619..c2a293f9 100644
--- a/indoteknik_custom/models/stock_immediate_transfer.py
+++ b/indoteknik_custom/models/stock_immediate_transfer.py
@@ -5,17 +5,20 @@ class StockImmediateTransfer(models.TransientModel):
_inherit = 'stock.immediate.transfer'
def process(self):
- """Override process method to add send_mail_bills logic."""
pickings_to_do = self.env['stock.picking']
pickings_not_to_do = self.env['stock.picking']
for line in self.immediate_transfer_line_ids:
+ line.picking_id.send_mail_bills()
+ line.picking_id.send_koli_to_so()
if line.to_immediate is True:
pickings_to_do |= line.picking_id
else:
pickings_not_to_do |= line.picking_id
for picking in pickings_to_do:
+ # picking.send_mail_bills()
+ # picking.send_koli_to_so()
# If still in draft => confirm and assign
if picking.state == 'draft':
picking.action_confirm()
@@ -23,6 +26,7 @@ class StockImmediateTransfer(models.TransientModel):
picking.action_assign()
if picking.state != 'assigned':
raise UserError(_("Could not reserve all requested products. Please use the 'Mark as Todo' button to handle the reservation manually."))
+
for move in picking.move_lines.filtered(lambda m: m.state not in ['done', 'cancel']):
for move_line in move.move_line_ids:
move_line.qty_done = move_line.product_uom_qty
@@ -32,4 +36,6 @@ class StockImmediateTransfer(models.TransientModel):
pickings_to_validate = self.env['stock.picking'].browse(pickings_to_validate)
pickings_to_validate = pickings_to_validate - pickings_not_to_do
return pickings_to_validate.with_context(skip_immediate=True).button_validate()
+
return True
+
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_move.py b/indoteknik_custom/models/stock_move.py
index 6b631713..90ab30a4 100644
--- a/indoteknik_custom/models/stock_move.py
+++ b/indoteknik_custom/models/stock_move.py
@@ -13,6 +13,37 @@ class StockMove(models.Model):
)
qr_code_variant = fields.Binary("QR Code Variant", compute='_compute_qr_code_variant')
barcode = fields.Char(string='Barcode', related='product_id.barcode')
+ vendor_id = fields.Many2one('res.partner' ,string='Vendor')
+ hold_outgoingg = fields.Boolean('Hold Outgoing', default=False)
+
+ # @api.model_create_multi
+ # def create(self, vals_list):
+ # moves = super(StockMove, self).create(vals_list)
+
+ # for move in moves:
+ # if move.product_id and move.location_id.id == 58 and move.location_dest_id.id == 57 and move.picking_type_id.id == 75:
+ # po_line = self.env['purchase.order.line'].search([
+ # ('product_id', '=', move.product_id.id),
+ # ('order_id.name', '=', move.origin)
+ # ], limit=1)
+ # if po_line:
+ # move.write({'purchase_line_id': po_line.id})
+
+ # return moves
+
+ @api.constrains('product_id')
+ def constrains_product_to_fill_vendor(self):
+ for rec in self:
+ if rec.product_id and rec.bom_line_id:
+ if rec.product_id.x_manufacture.override_vendor_id:
+ rec.vendor_id = rec.product_id.x_manufacture.override_vendor_id.id
+ else:
+ purchase_pricelist = self.env['purchase.pricelist'].search(
+ [('product_id', '=', rec.product_id.id),
+ ('is_winner', '=', True)],
+ limit=1)
+ if purchase_pricelist:
+ rec.vendor_id = purchase_pricelist.vendor_id.id
def _compute_qr_code_variant(self):
for rec in self:
diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py
index 36d9f63d..6a6fe352 100644
--- a/indoteknik_custom/models/stock_picking.py
+++ b/indoteknik_custom/models/stock_picking.py
@@ -1,7 +1,9 @@
from odoo import fields, models, api, _
from odoo.exceptions import AccessError, UserError, ValidationError
from odoo.tools.float_utils import float_is_zero
+from collections import defaultdict
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,11 +14,27 @@ import base64
import requests
import time
import logging
+import re
+
_logger = logging.getLogger(__name__)
+_biteship_url = "https://api.biteship.com/v1"
+_biteship_api_key = "biteship_test.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTcyOTQ5ODAwMX0.L6C73couP4-cgVEfhKI2g7eMCMo3YOFSRZhS-KSuHNA"
+
+
+# _biteship_api_key = "biteship_live.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiaW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTc0MTE1NTU4M30.pbFCai9QJv8iWhgdosf8ScVmEeP3e5blrn33CHe7Hgo"
+
+
class StockPicking(models.Model):
_inherit = 'stock.picking'
- check_product_lines = fields.One2many('check.product', 'picking_id', string='Check Product', auto_join=True)
+ _order = 'final_seq ASC'
+ konfirm_koli_lines = fields.One2many('konfirm.koli', 'picking_id', string='Konfirm Koli', auto_join=True,
+ copy=False)
+ scan_koli_lines = fields.One2many('scan.koli', 'picking_id', string='Scan Koli', auto_join=True, copy=False)
+ check_koli_lines = fields.One2many('check.koli', 'picking_id', string='Check Koli', auto_join=True, copy=False)
+
+ check_product_lines = fields.One2many('check.product', 'picking_id', string='Check Product', auto_join=True,
+ copy=False)
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')
account_id = fields.Many2one('account.account', string='Account')
@@ -62,36 +80,47 @@ class StockPicking(models.Model):
readonly=True,
copy=False
)
+ out_code = fields.Integer(
+ string="Out Code",
+ readonly=True,
+ related="id",
+ )
sj_documentation = fields.Binary(string="Dokumentasi Surat Jalan", )
paket_documentation = fields.Binary(string="Dokumentasi Paket", )
- sj_return_date = fields.Datetime(string="SJ Return Date", )
+ sj_return_date = fields.Datetime(string="SJ Return Date", copy=False)
responsible = fields.Many2one('res.users', string='Responsible', tracking=True)
approval_status = fields.Selection([
('pengajuan1', 'Approval Accounting'),
('approved', 'Approved'),
- ], string='Approval Status', readonly=True, copy=False, index=True, tracking=3, help="Approval Status untuk Internal Use")
+ ], string='Approval Status', readonly=True, copy=False, index=True, tracking=3,
+ help="Approval Status untuk Internal Use")
approval_receipt_status = fields.Selection([
('pengajuan1', 'Approval Logistic'),
('approved', 'Approved'),
- ], string='Approval Receipt Status', readonly=True, copy=False, index=True, tracking=3, help="Approval Status untuk Receipt")
+ ], string='Approval Receipt Status', readonly=True, copy=False, index=True, tracking=3,
+ help="Approval Status untuk Receipt")
approval_return_status = fields.Selection([
('pengajuan1', 'Approval Finance'),
('approved', 'Approved'),
- ], string='Approval Return Status', readonly=True, copy=False, index=True, tracking=3, help="Approval Status untuk Return")
- date_doc_kirim = fields.Datetime(string='Tanggal Kirim di SJ', help="Tanggal Kirim di cetakan SJ, tidak berpengaruh ke Accounting", tracking=True)
+ ], string='Approval Return Status', readonly=True, copy=False, index=True, tracking=3,
+ help="Approval Status untuk Return")
+ date_doc_kirim = fields.Datetime(string='Tanggal Kirim di SJ',
+ help="Tanggal Kirim di cetakan SJ, tidak berpengaruh ke Accounting", tracking=True,
+ copy=False)
note_logistic = fields.Selection([
- ('hold', 'Hold by Sales'),
+ ('wait_so_together', 'Tunggu SO Barengan'),
('not_paid', 'Customer belum bayar'),
- ('partial', 'Kirim Parsial'),
- ('indent', 'Indent'),
+ ('reserve_stock', 'Reserve Stock'),
+ ('waiting_schedule', 'Menunggu Jadwal Kirim'),
('self_pickup', 'Barang belum di pickup Customer'),
('expedition_closed', 'Eskpedisi belum buka')
], string='Note Logistic', help='jika field ini diisi maka tidak akan dihitung ke lead time')
waybill_id = fields.One2many(comodel_name='airway.bill', inverse_name='do_id', string='Airway Bill')
- purchase_representative_id = fields.Many2one('res.users', related='move_lines.purchase_line_id.order_id.user_id', string="Purchase Representative")
+ purchase_representative_id = fields.Many2one('res.users', related='move_lines.purchase_line_id.order_id.user_id',
+ string="Purchase Representative")
carrier_id = fields.Many2one('delivery.carrier', string='Shipping Method')
shipping_status = fields.Char(string='Shipping Status', compute="_compute_shipping_status")
date_reserved = fields.Datetime(string="Date Reserved", help='Tanggal ter-reserved semua barang nya', copy=False)
@@ -112,16 +141,74 @@ class StockPicking(models.Model):
('invoiced', 'Fully Invoiced'),
('to invoice', 'To Invoice'),
('no', 'Nothing to Invoice')
- ], string='Invoice Status', related="sale_id.invoice_status")
+ ], string='Invoice Status', related="sale_id.invoice_status")
note_return = fields.Text(string="Note Return", help="Catatan untuk kirim barang kembali")
-
+
state_reserve = fields.Selection([
('waiting', 'Waiting For Fullfilment'),
('ready', 'Ready to Ship'),
('done', 'Done'),
('cancel', 'Cancelled'),
], string='Status Reserve', tracking=True, copy=False, help="The current state of the stock picking.")
- notee = fields.Text(string="Note")
+ notee = fields.Text(string="Note SJ", help="Catatan untuk kirim barang")
+ note_info = fields.Text(string="Note Logistix (Text)", help="Catatan untuk pengiriman")
+ state_approve_md = fields.Selection([
+ ('waiting', 'Waiting For Approve by MD'),
+ ('pending', 'Pending (perlu koordinasi dengan MD)'),
+ ('done', 'Approve by MD'),
+ ], string='Approval MD Gudang Selisih', tracking=True, copy=False,
+ help="The current state of the MD Approval transfer barang from gudang selisih.")
+ # show_state_approve_md = fields.Boolean(compute="_compute_show_state_approve_md")
+
+ # def _compute_show_state_approve_md(self):
+ # for record in self:
+ # record.show_state_approve_md = record.location_id.id == 47 or record.location_id.complete_name == "Virtual Locations/Gudang Selisih"
+ quantity_koli = fields.Float(string="Quantity Koli", copy=False)
+ total_mapping_koli = fields.Float(string="Total Mapping Koli", compute='_compute_total_mapping_koli')
+ so_lama = fields.Boolean('SO LAMA', copy=False)
+ linked_manual_bu_out = fields.Many2one('stock.picking', string='BU Out', copy=False)
+
+ area_name = fields.Char(string="Area", compute="_compute_area_name")
+
+ @api.depends('real_shipping_id.kecamatan_id', 'real_shipping_id.kota_id')
+ def _compute_area_name(self):
+ for record in self:
+ district = record.real_shipping_id.kecamatan_id.name or ''
+ city = record.real_shipping_id.kota_id.name or ''
+ record.area_name = f"{district}, {city}".strip(', ')
+
+ # def write(self, vals):
+ # if 'linked_manual_bu_out' in vals:
+ # for record in self:
+ # if (record.picking_type_code == 'internal'
+ # and 'BU/PICK/' in record.name):
+ # # Jika menghapus referensi (nilai di-set False/None)
+ # if record.linked_manual_bu_out and not vals['linked_manual_bu_out']:
+ # record.linked_manual_bu_out.state_packing = 'not_packing'
+ # # Jika menambahkan referensi baru
+ # elif vals['linked_manual_bu_out']:
+ # new_picking = self.env['stock.picking'].browse(vals['linked_manual_bu_out'])
+ # new_picking.state_packing = 'packing_done'
+ # return super().write(vals)
+
+ # @api.model
+ # def create(self, vals):
+ # record = super().create(vals)
+ # if (record.picking_type_code == 'internal'
+ # and 'BU/PICK/' in record.name
+ # and vals.get('linked_manual_bu_out')):
+ # picking = self.env['stock.picking'].browse(vals['linked_manual_bu_out'])
+ # picking.state_packing = 'packing_done'
+ # return record
+
+ @api.depends('konfirm_koli_lines', 'konfirm_koli_lines.pick_id', 'konfirm_koli_lines.pick_id.quantity_koli')
+ def _compute_total_mapping_koli(self):
+ for record in self:
+ total = 0.0
+ for line in record.konfirm_koli_lines:
+ if line.pick_id and line.pick_id.quantity_koli:
+ total += line.pick_id.quantity_koli
+ record.total_mapping_koli = total
@api.model
def _compute_dokumen_tanda_terima(self):
@@ -133,8 +220,10 @@ class StockPicking(models.Model):
for picking in self:
picking.dokumen_pengiriman = picking.partner_id.dokumen_pengiriman_input
- dokumen_tanda_terima = fields.Char(string='Dokumen Tanda Terima yang Diberikan Pada Saat Pengiriman Barang', readonly=True, compute=_compute_dokumen_tanda_terima)
- dokumen_pengiriman = fields.Char(string='Dokumen yang Dibawa Saat Pengiriman Barang', readonly=True, compute=_compute_dokumen_pengiriman)
+ dokumen_tanda_terima = fields.Char(string='Dokumen Tanda Terima yang Diberikan Pada Saat Pengiriman Barang',
+ readonly=True, compute=_compute_dokumen_tanda_terima)
+ dokumen_pengiriman = fields.Char(string='Dokumen yang Dibawa Saat Pengiriman Barang', readonly=True,
+ compute=_compute_dokumen_pengiriman)
# Envio Tracking Section
envio_id = fields.Char(string="Envio ID", readonly=True)
@@ -166,6 +255,265 @@ 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")
+ # KGX Section
+ kgx_pod_photo_url = fields.Char('KGX Photo URL')
+ kgx_pod_photo = fields.Html('KGX Photo', compute='_compute_kgx_image_html')
+ kgx_pod_signature = fields.Char('KGX Signature URL')
+ kgx_pod_receive_time = fields.Datetime('KGX Ata Date')
+ kgx_pod_receiver = fields.Char('KGX Receiver')
+
+ total_koli = fields.Integer(compute='_compute_total_koli', string="Total Koli")
+ total_koli_display = fields.Char(compute='_compute_total_koli_display', string="Total Koli Display")
+ linked_out_picking_id = fields.Many2one('stock.picking', string="Linked BU/OUT", copy=False)
+ total_so_koli = fields.Integer(compute='_compute_total_so_koli', string="Total SO Koli")
+
+ # 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')
+ shipping_method_so_id = fields.Many2one('delivery.carrier', string='Shipping Method SO',
+ related='sale_id.carrier_id')
+ state_packing = fields.Selection([('not_packing', 'Belum Packing'), ('packing_done', 'Sudah Packing')],
+ string='Packing Status')
+ approval_invoice_date_id = fields.Many2one('approval.invoice.date', string='Approval Invoice Date')
+ last_update_date_doc_kirim = fields.Datetime(string='Last Update Tanggal Kirim')
+ update_date_doc_kirim_add = fields.Boolean(string='Update Tanggal Kirim Lewat ADD')
+
+ def _get_kgx_awb_number(self):
+ """Menggabungkan name dan origin untuk membuat AWB Number"""
+ self.ensure_one()
+ if not self.name or not self.origin:
+ return False
+ return f"{self.name} {self.origin}"
+
+ def _download_pod_photo(self, url):
+ """Mengunduh foto POD dari URL"""
+ try:
+ response = requests.get(url, timeout=10)
+ response.raise_for_status()
+ return base64.b64encode(response.content)
+ except Exception as e:
+ raise UserError(f"Gagal mengunduh foto POD: {str(e)}")
+
+ def _parse_datetime(self, dt_str):
+ """Parse datetime string dari format KGX"""
+ try:
+ from datetime import datetime
+ # Hilangkan timezone jika ada masalah parsing
+ if '+' in dt_str:
+ dt_str = dt_str.split('+')[0]
+ return datetime.strptime(dt_str, '%Y-%m-%dT%H:%M:%S')
+ except ValueError:
+ return False
+
+ def action_get_kgx_pod(self):
+ self.ensure_one()
+
+ awb_number = self._get_kgx_awb_number()
+ if not awb_number:
+ raise UserError("Nomor AWB tidak dapat dibuat, pastikan picking memiliki name dan origin")
+
+ url = "https://kgx.co.id/get_detail_awb"
+ headers = {'Content-Type': 'application/json'}
+ payload = {"params" : {'awb_number': awb_number}}
+
+ try:
+ response = requests.post(url, headers=headers, data=json.dumps(payload))
+ response.raise_for_status()
+ data = response.json()
+
+ if data.get('result', {}).get('data', []):
+ pod_data = data['result']['data'][0].get('connote_pod', {})
+ photo_url = pod_data.get('photo')
+
+ self.kgx_pod_photo_url = photo_url
+ self.kgx_pod_signature = pod_data.get('signature')
+ self.kgx_pod_receiver = pod_data.get('receiver')
+ self.kgx_pod_receive_time = self._parse_datetime(pod_data.get('timeReceive'))
+ self.driver_arrival_date = self._parse_datetime(pod_data.get('timeReceive'))
+
+ return data
+ else:
+ raise UserError(f"Tidak ditemukan data untuk AWB: {awb_number}")
+
+ except requests.exceptions.RequestException as e:
+ raise UserError(f"Gagal mengambil data POD: {str(e)}")
+
+ @api.constrains('sj_return_date')
+ def _check_sj_return_date(self):
+ for record in self:
+ if not record.driver_arrival_date:
+ if record.sj_return_date:
+ raise ValidationError(
+ _("Anda tidak dapat mengubah Tanggal Pengembalian setelah Tanggal Pengiriman!")
+ )
+
+ def _check_date_doc_kirim_modification(self):
+ for record in self:
+ if record.last_update_date_doc_kirim and not self.env.context.get('from_button_approve'):
+ kirim_date = fields.Datetime.from_string(record.last_update_date_doc_kirim)
+ now = fields.Datetime.now()
+
+ deadline = kirim_date + timedelta(days=1)
+ deadline = deadline.replace(hour=10, minute=0, second=0)
+
+ if now > deadline:
+ raise ValidationError(
+ _("Anda tidak dapat mengubah Tanggal Kirim setelah jam 10:00 pada hari berikutnya!")
+ )
+
+ @api.constrains('date_doc_kirim')
+ def _constrains_date_doc_kirim(self):
+ for rec in self:
+ rec.calculate_line_no()
+
+ if rec.picking_type_code == 'outgoing' and 'BU/OUT/' in rec.name and rec.partner_id.id != 96868:
+ invoice = self.env['account.move'].search(
+ [('sale_id', '=', rec.sale_id.id), ('move_type', '=', 'out_invoice'), ('state', '=', 'posted')],
+ limit=1, order='create_date desc')
+
+ if invoice and not self.env.context.get('active_model') == 'stock.picking':
+ rec._check_date_doc_kirim_modification()
+ if rec.date_doc_kirim != invoice.invoice_date and not self.env.context.get('from_button_approve'):
+ get_approval_invoice_date = self.env['approval.invoice.date'].search(
+ [('picking_id', '=', rec.id), ('state', '=', 'draft')], limit=1)
+
+ if get_approval_invoice_date and get_approval_invoice_date.state == 'draft':
+ get_approval_invoice_date.date_doc_do = rec.date_doc_kirim
+ else:
+ approval_invoice_date = self.env['approval.invoice.date'].create({
+ 'picking_id': rec.id,
+ 'date_invoice': invoice.invoice_date,
+ 'date_doc_do': rec.date_doc_kirim,
+ 'sale_id': rec.sale_id.id,
+ 'move_id': invoice.id,
+ 'partner_id': rec.partner_id.id
+ })
+
+ rec.approval_invoice_date_id = approval_invoice_date.id
+
+ if approval_invoice_date:
+ return {
+ 'type': 'ir.actions.client',
+ 'tag': 'display_notification',
+ 'params': {'title': 'Notification',
+ 'message': 'Invoice Date Tidak Sesuai, Document Approval Invoice Date Terbuat',
+ 'next': {'type': 'ir.actions.act_window_close'}},
+ }
+
+ rec.last_update_date_doc_kirim = datetime.datetime.utcnow()
+
+ @api.constrains('scan_koli_lines')
+ def _constrains_scan_koli_lines(self):
+ now = datetime.datetime.utcnow()
+ for picking in self:
+ if len(picking.scan_koli_lines) > 0:
+ if len(picking.scan_koli_lines) != picking.total_mapping_koli:
+ raise UserError("Scan Koli Tidak Sesuai Dengan Total Mapping Koli")
+
+ picking.driver_departure_date = now
+
+ @api.depends('total_so_koli')
+ def _compute_total_so_koli(self):
+ for picking in self:
+ if picking.state == 'done':
+ picking.total_so_koli = self.env['sales.order.koli'].search_count(
+ [('picking_id.linked_out_picking_id', '=', picking.id), ('state', '=', 'delivered')])
+ else:
+ picking.total_so_koli = self.env['sales.order.koli'].search_count(
+ [('picking_id.linked_out_picking_id', '=', picking.id), ('state', '!=', 'delivered')])
+
+ @api.depends('total_koli')
+ def _compute_total_koli(self):
+ for picking in self:
+ picking.total_koli = self.env['scan.koli'].search_count([('picking_id', '=', picking.id)])
+
+ @api.depends('total_koli', 'total_so_koli')
+ def _compute_total_koli_display(self):
+ for picking in self:
+ picking.total_koli_display = f"{picking.total_koli} / {picking.total_so_koli}"
+
+ @api.constrains('quantity_koli')
+ def _constrains_quantity_koli(self):
+ for picking in self:
+ if not picking.linked_out_picking_id:
+ so_koli = self.env['sales.order.koli'].search([('picking_id', '=', picking.id)])
+
+ if so_koli:
+ so_koli.unlink()
+
+ for rec in picking.check_koli_lines:
+ self.env['sales.order.koli'].create({
+ 'sale_order_id': picking.sale_id.id,
+ 'picking_id': picking.id,
+ 'koli_id': rec.id,
+ })
+ else:
+ raise UserError(
+ 'Tidak Bisa Mengubah Quantity Koli Karena Koli Dari Picking Ini Sudah Dipakai Di BU/OUT!')
+
+ @api.onchange('quantity_koli')
+ def _onchange_quantity_koli(self):
+ self.check_koli_lines = [(5, 0, 0)]
+ self.check_koli_lines = [(0, 0, {
+ 'koli': f"{self.name}/{str(i + 1).zfill(3)}",
+ 'picking_id': self.id,
+ }) for i in range(int(self.quantity_koli))]
+
+ 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:
@@ -173,13 +521,20 @@ class StockPicking(models.Model):
else:
record.lalamove_image_html = "No image available."
+ def _compute_kgx_image_html(self):
+ for record in self:
+ if record.kgx_pod_photo_url:
+ record.kgx_pod_photo = f'<img src="{record.kgx_pod_photo_url}" width="300" height="300"/>'
+ else:
+ record.kgx_pod_photo = "No image available."
+
def action_fetch_lalamove_order(self):
pickings = self.env['stock.picking'].search([
('picking_type_code', '=', 'outgoing'),
('state', '=', 'done'),
('carrier_id', '=', 9),
('lalamove_order_id', '!=', False)
- ])
+ ])
for picking in pickings:
try:
order_id = picking.lalamove_order_id
@@ -214,7 +569,7 @@ class StockPicking(models.Model):
for stop in stops:
pod = stop.get("POD", {})
if pod.get("status") == "DELIVERED":
- image_url = pod.get("image") # Sesuaikan jika key berbeda
+ image_url = pod.get("image") # Sesuaikan jika key berbeda
self.lalamove_image_url = image_url
address = stop.get("address")
@@ -235,7 +590,6 @@ class StockPicking(models.Model):
else:
raise UserError(f"Error {response.status_code}: {response.text}")
-
def _convert_to_wib(self, date_str):
"""
Mengonversi string waktu ISO 8601 ke format waktu Indonesia (WIB)
@@ -332,8 +686,9 @@ 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)])
@@ -359,17 +714,18 @@ class StockPicking(models.Model):
('order_id', '=', self.sale_id.id),
('product_id', '=', move_line.product_id.id)
], limit=1)
-
+
if order_line:
items_data_instant.append({
"name": order_line.product_id.name,
"description": order_line.name,
"value": order_line.price_unit,
- "quantity": move_line.qty_done, # Menggunakan qty_done dari move_line
+ "quantity": move_line.qty_done,
"weight": order_line.weight
})
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 +737,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,31 +746,57 @@ 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 == 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", "")
+
+ waybill_id = data.get("courier", {}).get("waybill_id", "")
- if response.status_code == 201:
- return response.json()
+ message = f"✅ Berhasil Order ke Biteship! Resi: {waybill_id}" if waybill_id else "⚠️ Order berhasil, tetapi tidak ada nomor resi."
+
+ return {
+ 'effect': {
+ 'fadeout': 'slow', # Efek menghilang perlahan
+ 'message': message, # Pesan sukses
+ 'type': 'rainbow_man', # Efek animasi lucu Odoo
+ }
+ }
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):
- if not self.date_doc_kirim:
+ def constrains_driver_departure_date(self):
+ if not self.date_doc_kirim:
self.date_doc_kirim = self.driver_departure_date
@api.constrains('arrival_time')
@@ -441,92 +824,64 @@ class StockPicking(models.Model):
if not self._context.get('darimana') == 'sale.order' and self.env.user.id not in users_in_group.mapped('id'):
self.sale_id.unreserve_id = self.id
return self._create_approval_notification('Logistic')
-
+
res = super(StockPicking, self).do_unreserve()
current_time = datetime.datetime.utcnow()
self.date_unreserve = current_time
- # self.check_state_reserve()
-
+
return res
-
- # def check_state_reserve(self):
- # do = self.search([
- # ('state', 'not in', ['cancel', 'draft', 'done']),
- # ('picking_type_code', '=', 'outgoing')
- # ])
-
- # for rec in do:
- # rec.state_reserve = 'ready'
- # rec.date_reserved = datetime.datetime.utcnow()
-
- # for line in rec.move_ids_without_package:
- # if line.product_uom_qty > line.reserved_availability:
- # rec.state_reserve = 'waiting'
- # rec.date_reserved = ''
- # break
def check_state_reserve(self):
pickings = self.search([
('state', 'not in', ['cancel', 'draft', 'done']),
- ('picking_type_code', '=', 'outgoing'),
- ('name', 'ilike', 'BU/OUT/'),
+ ('picking_type_code', '=', 'internal'),
+ ('name', 'ilike', 'BU/PICK/'),
])
- count = self.search_count([
- ('state', 'not in', ['cancel', 'draft', 'done']),
- ('picking_type_code', '=', 'outgoing')
- ])
-
for picking in pickings:
fullfillments = self.env['sales.order.fulfillment.v2'].search([
('sale_order_id', '=', picking.sale_id.id)
])
-
+
picking.state_reserve = 'ready'
picking.date_reserved = picking.date_reserved or datetime.datetime.utcnow()
-
+
if any(rec.so_qty != rec.reserved_stock_qty for rec in fullfillments):
picking.state_reserve = 'waiting'
picking.date_reserved = ''
-
+
self.check_state_reserve_backorder()
def check_state_reserve_backorder(self):
pickings = self.search([
('backorder_id', '!=', False),
- ('name', 'ilike', 'BU/OUT/'),
- ('picking_type_code', '=', 'outgoing'),
+ ('name', 'ilike', 'BU/PICK/'),
+ ('picking_type_code', '=', 'internal'),
('state', 'not in', ['cancel', 'draft', 'done'])
])
- count = self.search_count([
- ('backorder_id', '!=', False),
- ('picking_type_code', '=', 'outgoing'),
- ('state', 'not in', ['cancel', 'draft', 'done'])
- ])
-
for picking in pickings:
fullfillments = self.env['sales.order.fulfillment.v2'].search([
('sale_order_id', '=', picking.sale_id.id)
])
-
+
picking.state_reserve = 'ready'
picking.date_reserved = picking.date_reserved or datetime.datetime.utcnow()
-
+
if any(rec.so_qty != rec.reserved_stock_qty for rec in fullfillments):
picking.state_reserve = 'waiting'
picking.date_reserved = ''
-
+
def _create_approval_notification(self, approval_role):
title = 'Warning'
message = f'Butuh approval sales untuk unreserved'
return self._create_notification_action(title, message)
-
+
def _create_notification_action(self, title, message):
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
- 'params': { 'title': title, 'message': message, 'next': {'type': 'ir.actions.act_window_close'} },
+ 'params': {'title': title, 'message': message, 'next': {'type': 'ir.actions.act_window_close'}},
}
def _compute_shipping_status(self):
@@ -536,7 +891,7 @@ class StockPicking(models.Model):
status = 'shipment'
elif rec.driver_departure_date and (rec.sj_return_date or rec.driver_arrival_date):
status = 'completed'
-
+
rec.shipping_status = status
def action_create_invoice_from_mr(self):
@@ -544,10 +899,10 @@ class StockPicking(models.Model):
"""
if not self.env.user.is_accounting:
raise UserError('Hanya Accounting yang bisa membuat Bill')
-
+
precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
- #custom here
+ # custom here
po = self.env['purchase.order'].search([
('name', '=', self.group_id.name)
])
@@ -564,24 +919,29 @@ class StockPicking(models.Model):
invoice_vals = order._prepare_invoice()
# Invoice line values (keep only necessary sections).
for line in self.move_ids_without_package:
- po_line = self.env['purchase.order.line'].search([('order_id', '=', po.id), ('product_id', '=', line.product_id.id)], limit=1)
+ po_line = self.env['purchase.order.line'].search(
+ [('order_id', '=', po.id), ('product_id', '=', line.product_id.id)], limit=1)
qty = line.product_uom_qty
if po_line.display_type == 'line_section':
pending_section = line
continue
if not float_is_zero(po_line.qty_to_invoice, precision_digits=precision):
if pending_section:
- invoice_vals['invoice_line_ids'].append((0, 0, pending_section._prepare_account_move_line_from_mr(po_line, qty)))
+ invoice_vals['invoice_line_ids'].append(
+ (0, 0, pending_section._prepare_account_move_line_from_mr(po_line, qty)))
pending_section = None
- invoice_vals['invoice_line_ids'].append((0, 0, line._prepare_account_move_line_from_mr(po_line, qty)))
+ invoice_vals['invoice_line_ids'].append(
+ (0, 0, line._prepare_account_move_line_from_mr(po_line, qty)))
invoice_vals_list.append(invoice_vals)
if not invoice_vals_list:
- raise UserError(_('There is no invoiceable line. If a product has a control policy based on received quantity, please make sure that a quantity has been received.'))
+ raise UserError(
+ _('There is no invoiceable line. If a product has a control policy based on received quantity, please make sure that a quantity has been received.'))
# 2) group by (company_id, partner_id, currency_id) for batch creation
new_invoice_vals_list = []
- for grouping_keys, invoices in groupby(invoice_vals_list, key=lambda x: (x.get('company_id'), x.get('partner_id'), x.get('currency_id'))):
+ for grouping_keys, invoices in groupby(invoice_vals_list, key=lambda x: (
+ x.get('company_id'), x.get('partner_id'), x.get('currency_id'))):
origins = set()
payment_refs = set()
refs = set()
@@ -611,7 +971,8 @@ class StockPicking(models.Model):
# 4) Some moves might actually be refunds: convert them if the total amount is negative
# We do this after the moves have been created since we need taxes, etc. to know if the total
# is actually negative or not
- moves.filtered(lambda m: m.currency_id.round(m.amount_total) < 0).action_switch_invoice_into_refund_credit_note()
+ moves.filtered(
+ lambda m: m.currency_id.round(m.amount_total) < 0).action_switch_invoice_into_refund_credit_note()
return self.action_view_invoice_from_mr(moves)
@@ -674,7 +1035,7 @@ class StockPicking(models.Model):
# for stock_move_line in stock_move_lines:
# if stock_move_line.picking_id.state not in list_state:
# continue
- # raise UserError('Sudah pernah dikirim kalender')
+ # raise UserError('Sudah pernah dikirim kalender')
for pick in self:
if not pick.is_internal_use:
@@ -695,23 +1056,27 @@ class StockPicking(models.Model):
if self.env.user.is_accounting:
pick.approval_return_status = 'approved'
continue
+ else:
+ pick.approval_return_status = 'pengajuan1'
action = self.env['ir.actions.act_window']._for_xml_id('indoteknik_custom.action_stock_return_note_wizard')
if self.picking_type_code == 'outgoing':
if self.env.user.id in [3988, 3401, 20] or (
- self.env.user.has_group('indoteknik_custom.group_role_purchasing') and 'Return of' in self.origin
+ self.env.user.has_group(
+ 'indoteknik_custom.group_role_purchasing') and 'Return of' in self.origin
):
action['context'] = {'picking_ids': [x.id for x in self]}
return action
- elif not self.env.user.has_group('indoteknik_custom.group_role_purchasing') and 'Return of' in self.origin:
+ elif not self.env.user.has_group(
+ 'indoteknik_custom.group_role_purchasing') and 'Return of' in self.origin:
raise UserError('Harus Purchasing yang Ask Return')
else:
raise UserError('Harus Sales Admin yang Ask Return')
elif self.picking_type_code == 'incoming':
if self.env.user.has_group('indoteknik_custom.group_role_purchasing') or (
- self.env.user.id in [3988, 3401, 20] and 'Return of' in self.origin
+ self.env.user.id in [3988, 3401, 20] and 'Return of' in self.origin
):
action['context'] = {'picking_ids': [x.id for x in self]}
return action
@@ -721,7 +1086,7 @@ class StockPicking(models.Model):
raise UserError('Harus Purchasing yang Ask Return')
def calculate_line_no(self):
-
+
for picking in self:
name = picking.group_id.name
for move in picking.move_ids_without_package:
@@ -744,10 +1109,10 @@ class StockPicking(models.Model):
def _compute_summary_qty(self):
for picking in self:
sum_qty_detail = sum_qty_operation = count_line_detail = count_line_operation = 0
- for detail in picking.move_line_ids_without_package: # detailed operations
+ for detail in picking.move_line_ids_without_package: # detailed operations
sum_qty_detail += detail.qty_done
count_line_detail += 1
- for operation in picking.move_ids_without_package: # operations
+ for operation in picking.move_ids_without_package: # operations
sum_qty_operation += operation.product_uom_qty
count_line_operation += 1
picking.summary_qty_detail = sum_qty_detail
@@ -769,13 +1134,13 @@ class StockPicking(models.Model):
])
if (
- self.picking_type_id.id == 29
- and quant
- and line.location_id.id == bu_location_id
- and quant.inventory_quantity < line.product_uom_qty
+ self.picking_type_id.id == 29
+ and quant
+ and line.location_id.id == bu_location_id
+ and quant.inventory_quantity < line.product_uom_qty
):
raise UserError('Quantity reserved lebih besar dari quantity onhand di product')
-
+
def check_qty_done_stock(self):
for line in self.move_line_ids_without_package:
def check_qty_per_inventory(self, product, location):
@@ -788,12 +1153,74 @@ class StockPicking(models.Model):
return quant.quantity
return 0
-
+
qty_onhand = check_qty_per_inventory(self, line.product_id, line.location_id)
if line.qty_done > qty_onhand:
raise UserError('Quantity Done melebihi Quantity Onhand')
+ def button_state_approve_md(self):
+ group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id
+ users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])])
+ active_model = self.env.context.get('active_model')
+ if self.env.user.id in users_in_group.mapped('id'):
+ self.state_approve_md = 'done'
+ else:
+ raise UserError('Hanya MD yang bisa Approve')
+
+ def button_state_pending_md(self):
+ group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id
+ users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])])
+ active_model = self.env.context.get('active_model')
+ if self.env.user.id in users_in_group.mapped('id'):
+ self.state_approve_md = 'pending'
+ else:
+ raise UserError('Hanya MD yang bisa Approve')
+
def button_validate(self):
+ self.check_invoice_date()
+ threshold_datetime = waktu(2025, 4, 11, 6, 26)
+ group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id
+ users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])])
+ active_model = self.env.context.get('active_model')
+ if self.location_id.id == 47 and self.env.user.id not in users_in_group.mapped(
+ 'id') and self.state_approve_md != 'done':
+ self.state_approve_md = 'waiting' if self.state_approve_md != 'pending' else 'pending'
+ self.env.cr.commit()
+ raise UserError("Transfer dari gudang selisih harus di approve MD, Hubungi MD agar bisa di Validate")
+ else:
+ if self.location_id.id == 47 and self.env.user.id in users_in_group.mapped('id'):
+ self.state_approve_md = 'done'
+
+ if (len(self.konfirm_koli_lines) == 0
+ and 'BU/OUT/' in self.name
+ and self.picking_type_code == 'outgoing'
+ and self.create_date > threshold_datetime
+ and not self.so_lama):
+ raise UserError(_("Tidak ada Mapping koli! Harap periksa kembali."))
+
+ if (len(self.scan_koli_lines) == 0
+ and 'BU/OUT/' in self.name
+ and self.picking_type_code == 'outgoing'
+ and self.create_date > threshold_datetime
+ and not self.so_lama):
+ raise UserError(_("Tidak ada scan koli! Harap periksa kembali."))
+
+ # if self.driver_departure_date == False and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing':
+ # raise UserError(_("Isi Driver Departure Date dulu sebelum validate"))
+
+ if len(self.check_koli_lines) == 0 and 'BU/PICK/' in self.name:
+ raise UserError(_("Tidak ada koli! Harap periksa kembali."))
+
+ if not self.linked_manual_bu_out and 'BU/PICK/' in self.name:
+ raise UserError(_("Isi BU Out terlebih dahulu!"))
+
+ if len(self.check_product_lines) == 0 and 'BU/PICK/' in self.name:
+ raise UserError(_("Tidak ada Check Product! Harap periksa kembali."))
+
+ if self.total_koli > self.total_so_koli:
+ raise UserError(_("Total Koli (%s) dan Total SO Koli (%s) tidak sama! Harap periksa kembali.")
+ % (self.total_koli, self.t1otal_so_koli))
+
if not self.env.user.is_logistic_approver and self.env.context.get('active_model') == 'stock.picking':
if self.origin and 'Return of' in self.origin:
raise UserError("Button ini hanya untuk Logistik")
@@ -815,10 +1242,10 @@ class StockPicking(models.Model):
if self.is_internal_use and not self.env.user.is_accounting:
raise UserError("Harus di Approve oleh Accounting")
-
+
if self.picking_type_id.id == 28 and not self.env.user.is_logistic_approver:
raise UserError("Harus di Approve oleh Logistik")
-
+
if self.location_dest_id.id == 47 and not self.env.user.is_purchasing_manager:
raise UserError("Transfer ke gudang selisih harus di approve Rafly Hanggara")
@@ -841,13 +1268,130 @@ class StockPicking(models.Model):
self.validation_minus_onhand_quantity()
self.responsible = self.env.user.id
+ # self.send_koli_to_so()
+ if self.picking_type_code == 'outgoing' and 'BU/OUT/' in self.name:
+ self.check_koli()
res = super(StockPicking, self).button_validate()
- self.calculate_line_no()
self.date_done = datetime.datetime.utcnow()
self.state_reserve = 'done'
+ self.final_seq = 0
+ self.set_picking_code_out()
+ self.send_koli_to_so()
+
+ if (self.state_reserve == 'done' and self.picking_type_code == 'internal' and 'BU/PICK/' in self.name
+ and self.linked_manual_bu_out):
+ if not self.linked_manual_bu_out.date_reserved:
+ current_datetime = datetime.datetime.utcnow()
+ self.linked_manual_bu_out.date_reserved = current_datetime
+ self.linked_manual_bu_out.message_post(
+ body=f"Date Reserved diisi secara otomatis dari validasi BU/PICK {self.name}"
+ )
+
+ if not self.env.context.get('skip_koli_check'):
+ for picking in self:
+ if picking.sale_id:
+ all_koli_ids = picking.sale_id.koli_lines.filtered(lambda k: k.state != 'delivered').ids
+ scanned_koli_ids = picking.scan_koli_lines.mapped('koli_id.id')
+
+ missing_koli_ids = set(all_koli_ids) - set(scanned_koli_ids)
+
+ if len(missing_koli_ids) > 0 and picking.picking_type_code == 'outgoing' and 'BU/OUT/' in picking.name:
+ missing_koli_names = picking.sale_id.koli_lines.filtered(
+ lambda k: k.id in missing_koli_ids and k.state != 'delivered').mapped('display_name')
+ missing_koli_list = "\n".join(f"- {name}" for name in missing_koli_names)
+
+ # Buat wizard modal warning
+ wizard = self.env['warning.modal.wizard'].create({
+ 'message': f"Berikut Koli yang belum discan:\n{missing_koli_list}",
+ 'picking_id': picking.id,
+ })
+
+ return {
+ 'type': 'ir.actions.act_window',
+ 'res_model': 'warning.modal.wizard',
+ 'view_mode': 'form',
+ 'res_id': wizard.id,
+ 'target': 'new',
+ }
self.send_mail_bills()
return res
+ def check_invoice_date(self):
+ for picking in self:
+ if picking.picking_type_code != 'outgoing' or 'BU/OUT/' not in picking.name or picking.partner_id.id == 96868:
+ continue
+
+ invoice = self.env['account.move'].search(
+ [('sale_id', '=', picking.sale_id.id), ('state', 'not in', ['draft', 'cancel']), ('move_type', '=', 'out_invoice')], limit=1)
+
+ if not invoice:
+ continue
+
+ if not picking.so_lama and invoice and (not picking.date_doc_kirim or not invoice.invoice_date):
+ raise UserError("Tanggal Kirim atau Tanggal Invoice belum diisi!")
+
+ picking_date = fields.Date.to_date(picking.date_doc_kirim)
+ invoice_date = fields.Date.to_date(invoice.invoice_date)
+
+ if picking_date != invoice_date and picking.update_date_doc_kirim_add:
+ raise UserError("Tanggal Kirim (%s) tidak sesuai dengan Tanggal Invoice (%s)!" % (
+ picking_date.strftime('%d-%m-%Y'),
+ invoice_date.strftime('%d-%m-%Y')
+ ))
+
+ def set_picking_code_out(self):
+ for picking in self:
+ # Check if picking meets criteria
+ is_bu_pick = picking.picking_type_code == 'internal' and 'BU/PICK/' in picking.name
+ if not is_bu_pick:
+ continue
+
+ # Find matching outgoing transfers
+ bu_out_transfers = self.search([
+ ('name', 'like', 'BU/OUT/%'),
+ ('sale_id', '=', picking.sale_id.id),
+ ('picking_type_code', '=', 'outgoing'),
+ ('picking_code', '=', False),
+ ('state', 'not in', ['done', 'cancel'])
+ ])
+
+ # Assign sequence code to each matching transfer
+ for transfer in bu_out_transfers:
+ transfer.picking_code = self.env['ir.sequence'].next_by_code('stock.picking.code')
+
+ def check_koli(self):
+ for picking in self:
+ sale_id = picking.sale_id
+ for koli_lines in picking.scan_koli_lines:
+ if koli_lines.koli_id.sale_order_id != sale_id:
+ raise UserError('Koli tidak sesuai')
+
+ def send_koli_to_so(self):
+ for picking in self:
+ if picking.picking_type_code == 'internal' and 'BU/PICK/' in picking.name:
+ for koli_line in picking.check_koli_lines:
+ existing_koli = self.env['sales.order.koli'].search([
+ ('sale_order_id', '=', picking.sale_id.id),
+ ('picking_id', '=', picking.id),
+ ('koli_id', '=', koli_line.id)
+ ], limit=1)
+
+ if not existing_koli:
+ self.env['sales.order.koli'].create({
+ 'sale_order_id': picking.sale_id.id,
+ 'picking_id': picking.id,
+ 'koli_id': koli_line.id
+ })
+
+ if picking.picking_type_code == 'outgoing' and 'BU/OUT/' in picking.name:
+ if picking.state == 'done':
+ for koli_line in picking.scan_koli_lines:
+ existing_koli = self.env['sales.order.koli'].search([
+ ('sale_order_id', '=', picking.sale_id.id),
+ ('koli_id', '=', koli_line.koli_id.koli_id.id)
+ ], limit=1)
+
+ existing_koli.state = 'delivered'
def check_qty_done_stock(self):
for line in self.move_line_ids_without_package:
@@ -861,7 +1405,7 @@ class StockPicking(models.Model):
return quant.quantity
return 0
-
+
qty_onhand = check_qty_per_inventory(self, line.product_id, line.location_id)
if line.qty_done > qty_onhand:
raise UserError('Quantity Done melebihi Quantity Onhand')
@@ -918,32 +1462,56 @@ class StockPicking(models.Model):
return True
def action_cancel(self):
- if not self.env.user.is_logistic_approver and self.env.context.get('active_model') == 'stock.picking':
+ if not self.env.user.is_logistic_approver and (
+ self.env.context.get('active_model') == 'stock.picking' or self.env.context.get(
+ 'active_model') == 'stock.picking.type'):
if self.origin and 'Return of' in self.origin:
raise UserError("Button ini hanya untuk Logistik")
+ if not self.env.user.has_group('indoteknik_custom.group_role_it') and not self.env.user.has_group(
+ 'indoteknik_custom.group_role_logistic') and self.picking_type_code == 'outgoing':
+ raise UserError("Button ini hanya untuk Logistik")
+
res = super(StockPicking, self).action_cancel()
return res
-
@api.model
def create(self, vals):
self._use_faktur(vals)
- if vals.get('picking_type_code') == 'incoming' and vals.get('location_dest_id') == 58:
- if 'name' in vals and vals['name'].startswith('BU/IN/'):
- vals['name'] = vals['name'].replace('BU/IN/', 'BU/INPUT/', 1)
-
- if vals.get('picking_type_code') == 'internal' and vals.get('location_id') == 58:
- if 'name' in vals and vals['name'].startswith('BU/INT'):
- new_name = vals['name'].replace('BU/INT', 'BU/IN', 1)
- # Periksa apakah nama sudah ada
- if self.env['stock.picking'].search_count([('name', '=', new_name), ('company_id', '=', vals.get('company_id'))]) > 0:
- new_name = f"{new_name}-DUP"
- vals['name'] = new_name
- return super(StockPicking, self).create(vals)
+ records = super(StockPicking, self).create(vals)
+
+ # Panggil sync_sale_line setelah record dibuat
+ # records.sync_sale_line(vals)
+ return records
+
+ def sync_sale_line(self, vals):
+ # Pastikan kita bekerja dengan record yang sudah ada
+ for picking in self:
+ if picking.picking_type_code == 'internal' and 'BU/PICK/' in picking.name:
+ for line in picking.move_ids_without_package:
+ if line.product_id and picking.sale_id:
+ sale_line = self.env['sale.order.line'].search([
+ ('product_id', '=', line.product_id.id),
+ ('order_id', '=', picking.sale_id.id)
+ ], limit=1) # Tambahkan limit=1 untuk efisiensi
+
+ if sale_line:
+ line.sale_line_id = sale_line.id
def write(self, vals):
+ if 'linked_manual_bu_out' in vals:
+ for record in self:
+ if (record.picking_type_code == 'internal'
+ and 'BU/PICK/' in record.name):
+ # Jika menghapus referensi (nilai di-set False/None)
+ if record.linked_manual_bu_out and not vals['linked_manual_bu_out']:
+ record.linked_manual_bu_out.state_packing = 'not_packing'
+ # Jika menambahkan referensi baru
+ elif vals['linked_manual_bu_out']:
+ new_picking = self.env['stock.picking'].browse(vals['linked_manual_bu_out'])
+ new_picking.state_packing = 'packing_done'
self._use_faktur(vals)
+ self.sync_sale_line(vals)
for picking in self:
# Periksa apakah kondisi terpenuhi saat data diubah
if (vals.get('picking_type_code', picking.picking_type_code) == 'incoming' and
@@ -959,7 +1527,8 @@ class StockPicking(models.Model):
if name_to_modify.startswith('BU/INT'):
new_name = name_to_modify.replace('BU/INT', 'BU/IN', 1)
# Periksa apakah nama sudah ada
- if self.env['stock.picking'].search_count([('name', '=', new_name), ('company_id', '=', picking.company_id.id)]) > 0:
+ if self.env['stock.picking'].search_count(
+ [('name', '=', new_name), ('company_id', '=', picking.company_id.id)]) > 0:
new_name = f"{new_name}-DUP"
vals['name'] = new_name
return super(StockPicking, self).write(vals)
@@ -1003,7 +1572,7 @@ class StockPicking(models.Model):
def get_manifests(self):
if self.waybill_id and len(self.waybill_id.manifest_ids) > 0:
return [self.create_manifest_data(x.description, x.datetime) for x in self.waybill_id.manifest_ids]
-
+
status_mapping = {
'pickup': {
'arrival': 'Sudah diambil',
@@ -1028,7 +1597,7 @@ class StockPicking(models.Model):
if not status:
return manifest_datas
-
+
if arrival_date or self.sj_return_date:
manifest_datas.append(self.create_manifest_data(status['arrival'], arrival_date))
if departure_date:
@@ -1040,10 +1609,13 @@ 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,20 +1624,105 @@ 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
return response
-
+
response['delivery_order']['receiver_name'] = self.waybill_id.receiver_name
response['delivery_order']['receiver_city'] = self.waybill_id.receiver_city
response['delivery_status'] = self.waybill_id._get_history('delivery_status')
response['delivered'] = self.waybill_id.delivered
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": 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
@@ -1079,7 +1736,7 @@ class StockPicking(models.Model):
fastest_eta = start_date + ead_datetime
if not self.driver_departure_date and fastest_eta < current_date:
fastest_eta = current_date + ead_datetime
-
+
longest_days = 3
longest_eta = fastest_eta + datetime.timedelta(days=longest_days)
@@ -1088,9 +1745,10 @@ class StockPicking(models.Model):
formatted_fastest_eta = fastest_eta.strftime(format_time_fastest)
formatted_longest_eta = longest_eta.strftime(format_time)
-
+
return f'{formatted_fastest_eta} - {formatted_longest_eta}'
-
+
+
class CheckProduct(models.Model):
_name = 'check.product'
_description = 'Check Product'
@@ -1104,9 +1762,73 @@ class CheckProduct(models.Model):
index=True,
copy=False,
)
- product_id = fields.Many2one('product.product', string='Product', required=True)
- quantity = fields.Float(string='Quantity', default=1.0, required=True)
+ product_id = fields.Many2one('product.product', string='Product')
+ quantity = fields.Float(string='Quantity')
status = fields.Char(string='Status', compute='_compute_status')
+ code_product = fields.Char(string='Code Product')
+
+ @api.onchange('code_product')
+ def _onchange_code_product(self):
+ if not self.code_product:
+ return
+
+ # Cari product berdasarkan default_code, barcode, atau barcode_box
+ product = self.env['product.product'].search([
+ '|',
+ ('default_code', '=', self.code_product),
+ '|',
+ ('barcode', '=', self.code_product),
+ ('barcode_box', '=', self.code_product)
+ ], limit=1)
+
+ if not product:
+ raise UserError("Product tidak ditemukan")
+
+ # Jika scan barcode_box, set quantity sesuai qty_pcs_box
+ if product.barcode_box == self.code_product:
+ self.product_id = product.id
+ self.quantity = product.qty_pcs_box
+ self.code_product = product.default_code or product.barcode
+ # return {
+ # 'warning': {
+ # 'title': 'Info',8994175025871
+
+ # 'message': f'Product box terdeteksi. Quantity di-set ke {product.qty_pcs_box}'
+ # }
+ # }
+ else:
+ # Jika scan biasa
+ self.product_id = product.id
+ self.code_product = product.default_code or product.barcode
+ self.quantity = 1
+
+ def unlink(self):
+ # Get all affected pickings before deletion
+ pickings = self.mapped('picking_id')
+
+ # Store product_ids that will be deleted
+ deleted_product_ids = self.mapped('product_id')
+
+ # Perform the deletion
+ result = super(CheckProduct, self).unlink()
+
+ # After deletion, update moves for affected pickings
+ for picking in pickings:
+ # For products that were completely removed (no remaining check.product lines)
+ remaining_product_ids = picking.check_product_lines.mapped('product_id')
+ removed_product_ids = deleted_product_ids - remaining_product_ids
+
+ # Set quantity_done to 0 for moves of completely removed products
+ moves_to_reset = picking.move_ids_without_package.filtered(
+ lambda move: move.product_id in removed_product_ids
+ )
+ for move in moves_to_reset:
+ move.quantity_done = 0.0
+
+ # Also sync remaining products in case their totals changed
+ self._sync_check_product_to_moves(picking)
+
+ return result
@api.depends('quantity')
def _compute_status(self):
@@ -1121,7 +1843,6 @@ class CheckProduct(models.Model):
else:
record.status = 'Done'
-
def create(self, vals):
# Create the record
record = super(CheckProduct, self).create(vals)
@@ -1146,7 +1867,8 @@ class CheckProduct(models.Model):
for product_id in picking.check_product_lines.mapped('product_id'):
# Totalkan quantity dari semua baris check.product untuk product_id ini
total_quantity = sum(
- line.quantity for line in picking.check_product_lines.filtered(lambda line: line.product_id == product_id)
+ line.quantity for line in
+ picking.check_product_lines.filtered(lambda line: line.product_id == product_id)
)
# Update quantity_done di move yang relevan
moves = picking.move_ids_without_package.filtered(lambda move: move.product_id == product_id)
@@ -1199,14 +1921,14 @@ class CheckProduct(models.Model):
if not moves:
raise UserError((
- "The product '%s' tidak ada di operations. "
- ) % record.product_id.display_name)
+ "The product '%s' tidak ada di operations. "
+ ) % record.product_id.display_name)
total_qty_in_moves = sum(moves.mapped('product_uom_qty'))
# Find existing lines for the same product, excluding the current line
existing_lines = record.picking_id.check_product_lines.filtered(
- lambda line: line.product_id == record.product_id and line.id != record.id
+ lambda line: line.product_id == record.product_id
)
if existing_lines:
@@ -1214,22 +1936,23 @@ class CheckProduct(models.Model):
first_line = existing_lines[0]
# Calculate the total quantity after addition
- total_quantity = sum(existing_lines.mapped('quantity')) - record.quantity
+ total_quantity = sum(existing_lines.mapped('quantity'))
if total_quantity > total_qty_in_moves:
raise UserError((
- "Quantity Product '%s' sudah melebihi quantity demand."
- ) % (record.product_id.display_name))
+ "Quantity Product '%s' sudah melebihi quantity demand."
+ ) % (record.product_id.display_name))
else:
# Check if the quantity exceeds the allowed total
- if record.quantity > total_qty_in_moves:
+ if record.quantity == total_qty_in_moves:
raise UserError((
- "Quantity Product '%s' sudah melebihi quantity demand."
- ) % (record.product_id.display_name))
+ "Quantity Product '%s' sudah melebihi quantity demand."
+ ) % (record.product_id.display_name))
# Set the quantity to the entered value
record.quantity = record.quantity
+
class BarcodeProduct(models.Model):
_name = 'barcode.product'
_description = 'Barcode Product'
@@ -1246,10 +1969,286 @@ class BarcodeProduct(models.Model):
product_id = fields.Many2one('product.product', string='Product', required=True)
barcode = fields.Char(string='Barcode')
+ def check_duplicate_barcode(self):
+ barcode_product = self.env['product.product'].search([('barcode', '=', self.barcode)])
+
+ if barcode_product:
+ raise UserError('Barcode sudah digunakan {}'.format(barcode_product.display_name))
+
+ barcode_box = self.env['product.product'].search([('barcode_box', '=', self.barcode)])
+
+ if barcode_box:
+ raise UserError('Barcode box sudah digunakan {}'.format(barcode_box.display_name))
+
@api.constrains('barcode')
def send_barcode_to_product(self):
for record in self:
+ record.check_duplicate_barcode()
if record.barcode and not record.product_id.barcode:
record.product_id.barcode = record.barcode
else:
- raise UserError('Barcode sudah terisi') \ No newline at end of file
+ raise UserError('Barcode sudah terisi')
+
+
+class CheckKoli(models.Model):
+ _name = 'check.koli'
+ _description = 'Check Koli'
+ _order = 'picking_id, id'
+ _rec_name = 'koli'
+
+ picking_id = fields.Many2one(
+ 'stock.picking',
+ string='Picking Reference',
+ required=True,
+ ondelete='cascade',
+ index=True,
+ copy=False,
+ )
+ koli = fields.Char(string='Koli')
+ reserved_id = fields.Many2one('stock.picking', string='Reserved Picking')
+ check_koli_progress = fields.Char(
+ string="Progress Check Koli"
+ )
+
+ @api.constrains('koli')
+ def _check_koli_progress(self):
+ for check in self:
+ if check.picking_id:
+ all_checks = self.env['check.koli'].search([('picking_id', '=', check.picking_id.id)], order='id')
+ if all_checks:
+ check_index = list(all_checks).index(check) + 1 # Nomor urut check
+ total_so_koli = len(all_checks)
+ check.check_koli_progress = f"{check_index}/{total_so_koli}" if total_so_koli else "0/0"
+
+
+class ScanKoli(models.Model):
+ _name = 'scan.koli'
+ _description = 'Scan Koli'
+ _order = 'picking_id, id'
+ _rec_name = 'koli_id'
+
+ picking_id = fields.Many2one(
+ 'stock.picking',
+ string='Picking Reference',
+ required=True,
+ ondelete='cascade',
+ index=True,
+ copy=False,
+ )
+ koli_id = fields.Many2one('sales.order.koli', string='Koli')
+ scan_koli_progress = fields.Char(
+ string="Progress Scan Koli",
+ compute="_compute_scan_koli_progress"
+ )
+ code_koli = fields.Char(string='Code Koli')
+
+ @api.onchange('code_koli')
+ def _onchange_code_koli(self):
+ if self.code_koli:
+ koli = self.env['sales.order.koli'].search([('koli_id.koli', '=', self.code_koli)], limit=1)
+ if koli:
+ self.write({'koli_id': koli.id})
+ else:
+ raise UserError('Koli tidak ditemukan')
+
+ # def _compute_scan_koli_progress(self):
+ # for scan in self:
+ # if scan.picking_id:
+ # all_scans = self.env['scan.koli'].search([('picking_id', '=', scan.picking_id.id)], order='id')
+ # if all_scans:
+ # scan_index = list(all_scans).index(scan) + 1 # Nomor urut scan
+ # total_so_koli = scan.picking_id.total_so_koli
+ # scan.scan_koli_progress = f"{scan_index}/{total_so_koli}" if total_so_koli else "0/0"
+
+ @api.onchange('koli_id')
+ def _onchange_koli_compare_with_konfirm_koli(self):
+ if not self.koli_id:
+ return
+
+ if not self.picking_id.konfirm_koli_lines:
+ raise UserError(_('Mapping Koli Harus Diisi!'))
+
+ koli_picking = self.koli_id.picking_id._origin
+
+ konfirm_pick_ids = [
+ line.pick_id._origin
+ for line in self.picking_id.konfirm_koli_lines
+ if line.pick_id
+ ]
+
+ if koli_picking not in konfirm_pick_ids:
+ raise UserError(_('Koli tidak sesuai dengan mapping koli, pastikan picking terkait benar!'))
+
+ @api.constrains('picking_id', 'koli_id')
+ def _check_duplicate_koli(self):
+ for record in self:
+ if record.koli_id:
+ existing_koli = self.search([
+ ('picking_id', '=', record.picking_id.id),
+ ('koli_id', '=', record.koli_id.id),
+ ('id', '!=', record.id)
+ ])
+ if existing_koli:
+ raise ValidationError(f"⚠️ Koli '{record.koli_id.display_name}' sudah discan untuk picking ini!")
+
+ def unlink(self):
+ picking_ids = set(self.mapped('koli_id.picking_id.id'))
+ for scan in self:
+ koli = scan.koli_id.koli_id
+ if koli:
+ koli.reserved_id = False
+
+ for picking_id in picking_ids:
+ remaining_scans = self.env['sales.order.koli'].search_count([
+ ('koli_id.picking_id', '=', picking_id)
+ ])
+
+ delete_koli = len(self.filtered(lambda rec: rec.koli_id.picking_id.id == picking_id))
+
+ if remaining_scans == delete_koli:
+ picking = self.env['stock.picking'].browse(picking_id)
+ picking.linked_out_picking_id = False
+ else:
+ raise UserError(
+ _("Tidak dapat menghapus scan koli, karena masih ada scan koli lain yang tersisa untuk picking ini."))
+
+ for picking_id in picking_ids:
+ self._reset_qty_done_if_no_scan(picking_id)
+
+ # self.check_koli_not_balance()
+
+ return super(ScanKoli, self).unlink()
+
+ @api.onchange('koli_id', 'scan_koli_progress')
+ def onchange_koli_id(self):
+ if not self.koli_id:
+ return
+
+ for scan in self:
+ if scan.koli_id.koli_id.picking_id.group_id.id != scan.picking_id.group_id.id:
+ scan.koli_id.koli_id.reserved_id = scan.picking_id.id.origin
+ scan.koli_id.koli_id.picking_id.linked_out_picking_id = scan.picking_id.id.origin
+
+ def _compute_scan_koli_progress(self):
+ for scan in self:
+ if not scan.picking_id:
+ scan.scan_koli_progress = "0/0"
+ continue
+
+ try:
+ all_scans = self.env['scan.koli'].search([('picking_id', '=', scan.picking_id.id)], order='id')
+ if all_scans:
+ scan_index = list(all_scans).index(scan) + 1
+ total_so_koli = scan.picking_id.total_so_koli or 0
+ scan.scan_koli_progress = f"{scan_index}/{total_so_koli}"
+ else:
+ scan.scan_koli_progress = "0/0"
+ except Exception:
+ # Fallback in case of any error
+ scan.scan_koli_progress = "0/0"
+
+ @api.constrains('picking_id', 'picking_id.total_so_koli')
+ def _check_koli_validation(self):
+ for scan in self.picking_id.scan_koli_lines:
+ scan.koli_id.koli_id.reserved_id = scan.picking_id.id
+ scan.koli_id.koli_id.picking_id.linked_out_picking_id = scan.picking_id.id
+
+ total_scans = len(self.picking_id.scan_koli_lines)
+ if total_scans != self.picking_id.total_so_koli:
+ raise UserError(_("Jumlah scan koli tidak sama dengan total SO koli!"))
+
+ # def check_koli_not_balance(self):
+ # for scan in self:
+ # total_scancs = self.env['scan.koli'].search_count([('picking_id', '=', scan.picking_id.id), ('id', '!=', scan.id)])
+ # if total_scancs != scan.picking_id.total_so_koli:
+ # raise UserError(_("Jumlah scan koli tidak sama dengan total SO koli!"))
+
+ @api.onchange('koli_id')
+ def _onchange_koli_id(self):
+ if not self.koli_id:
+ return
+
+ source_koli_so = self.picking_id.group_id.id
+ source_koli = self.koli_id.picking_id.group_id.id
+
+ if source_koli_so != source_koli:
+ raise UserError(_('Koli tidak sesuai, pastikan picking terkait benar!'))
+
+ @api.constrains('koli_id')
+ def _send_product_from_koli_id(self):
+ if not self.koli_id:
+ return
+
+ koli_count_by_picking = defaultdict(int)
+ for scan in self:
+ koli_count_by_picking[scan.koli_id.picking_id.id] += 1
+
+ for picking_id, total_koli in koli_count_by_picking.items():
+ picking = self.env['stock.picking'].browse(picking_id)
+
+ if total_koli == picking.quantity_koli:
+ pick_moves = self.env['stock.move.line'].search([('picking_id', '=', picking_id)])
+ out_moves = self.env['stock.move.line'].search([('picking_id', '=', picking.linked_out_picking_id.id)])
+
+ for pick_move in pick_moves:
+ corresponding_out_move = out_moves.filtered(lambda m: m.product_id == pick_move.product_id)
+ if corresponding_out_move:
+ corresponding_out_move.qty_done += pick_move.qty_done
+
+ def _reset_qty_done_if_no_scan(self, picking_id):
+ product_bu_pick = self.env['stock.move.line'].search([('picking_id', '=', picking_id)])
+
+ for move in product_bu_pick:
+ product_bu_out = self.env['stock.move.line'].search(
+ [('picking_id', '=', self.picking_id.id), ('product_id', '=', move.product_id.id)])
+ for bu_out in product_bu_out:
+ bu_out.qty_done -= move.qty_done
+ # if remaining_scans == 0:
+ # picking = self.env['stock.picking'].browse(picking_id)
+ # picking.move_line_ids_without_package.write({'qty_done': 0})
+ # picking.message_post(body=f"⚠ qty_done direset ke 0 untuk Picking {picking.name} karena tidak ada scan.koli yang tersisa.")
+
+ # return remaining_scans
+
+
+class KonfirmKoli(models.Model):
+ _name = 'konfirm.koli'
+ _description = 'Konfirm Koli'
+ _order = 'picking_id, id'
+ _rec_name = 'pick_id'
+
+ picking_id = fields.Many2one(
+ 'stock.picking',
+ string='Picking Reference',
+ required=True,
+ ondelete='cascade',
+ index=True,
+ copy=False,
+ )
+ pick_id = fields.Many2one('stock.picking', string='Pick')
+
+ @api.constrains('pick_id')
+ def _check_duplicate_pick_id(self):
+ for rec in self:
+ exist = self.search([
+ ('pick_id', '=', rec.pick_id.id),
+ ('picking_id', '=', rec.picking_id.id),
+ ('id', '!=', rec.id),
+ ])
+
+ if exist:
+ raise UserError(f"⚠️ '{rec.pick_id.display_name}' sudah discan untuk picking ini!")
+
+
+class WarningModalWizard(models.TransientModel):
+ _name = 'warning.modal.wizard'
+ _description = 'Peringatan Koli Belum Diperiksa'
+
+ name = fields.Char(default="⚠️ Perhatian!")
+ message = fields.Text()
+ picking_id = fields.Many2one('stock.picking')
+
+ def action_continue(self):
+ if self.picking_id:
+ return self.picking_id.with_context(skip_koli_check=True).button_validate()
+ return {'type': 'ir.actions.act_window_close'}
diff --git a/indoteknik_custom/models/stock_picking_return.py b/indoteknik_custom/models/stock_picking_return.py
index d4347235..a683d80e 100644
--- a/indoteknik_custom/models/stock_picking_return.py
+++ b/indoteknik_custom/models/stock_picking_return.py
@@ -24,4 +24,15 @@ class ReturnPicking(models.TransientModel):
# if not stock_picking.approval_return_status == 'approved' and purchase.invoice_ids:
# raise UserError('Harus Approval Accounting AP untuk melakukan Retur')
- return res \ No newline at end of file
+ return res
+
+class ReturnPickingLine(models.TransientModel):
+ _inherit = 'stock.return.picking.line'
+
+ @api.onchange('quantity')
+ def _onchange_quantity(self):
+ for rec in self:
+ qty_done = rec.move_id.quantity_done
+
+ if rec.quantity > qty_done:
+ raise UserError(f"Quantity yang Anda masukkan tidak boleh melebihi quantity done yaitu: {qty_done}") \ No newline at end of file
diff --git a/indoteknik_custom/models/user_form_merchant.py b/indoteknik_custom/models/user_form_merchant.py
new file mode 100644
index 00000000..a804e93f
--- /dev/null
+++ b/indoteknik_custom/models/user_form_merchant.py
@@ -0,0 +1,98 @@
+from odoo import models, fields, api
+from odoo.exceptions import UserError
+from odoo.http import request
+
+
+class UserFormMerchant(models.Model):
+ _name = 'user.form.merchant'
+ _inherit = ['mail.thread', 'mail.activity.mixin']
+
+ name = fields.Char(string='Name')
+ # informasi peruhsaan
+ name_merchant = fields.Char(string='Name')
+ pejabat_name = fields.Char(string='Pejabat Name')
+ pic_merchant = fields.Char(string='PIC Merchant')
+ pic_position = fields.Char(string='Jabatan PIC')
+ partner_id = fields.Many2one('res.partner', string='Company')
+ address = fields.Char(string='Alamat')
+ state = fields.Many2one('res.country.state', string='State')
+ city = fields.Many2one('vit.kota', string='Kota')
+ district = fields.Many2one('vit.kecamatan', string='Kecamatan')
+ subDistrict = fields.Many2one('vit.kelurahan', string='Kelurahan')
+ zip = fields.Char(string='Kode Pos')
+ bank_name = fields.Char(string='Nama Bank')
+ rekening_name = fields.Char(string='Nama Rekening')
+ account_number = fields.Char(string='Nomor Rekening Bank')
+ email_company = fields.Char(string='Email Perusahaan')
+ email_sales = fields.Char(string='Email Sales')
+ email_finance = fields.Char(string='Email Finance')
+ phone = fields.Char(string='No. Telepon Perusahaan')
+ mobile = fields.Char(string='No. Handphone')
+ bisnis_type = fields.Selection([
+ ('1', 'PT'),
+ ('2', 'CV'),
+ ('3', 'Perorangan'),
+ ])
+ website = fields.Char(string='Website')
+ category_perusahaan = fields.Selection([
+ ('1', 'Principal (Pemegang merk/Produsen)'),
+ ('2', 'Sole Distributor (Distributor Tunggal)'),
+ ('3', 'Authorized Distributor (Distributor Resmi)'),
+ ('4', 'Importer (Pengimpor Barang)'),
+ ('5', 'Wholesaler (Pedagang Besar)'),
+ ])
+ description = fields.Text(string='Deskripsi')
+
+ # imformasi Vendor
+ harga_tayang = fields.Char(string='Harga Tayang (HET)')
+ category_produk_ids = fields.Many2many('product.public.category', string='Kategori Produk yang Digunakan',
+ domain=lambda self: self._get_default_category_domain())
+
+ @api.model
+ def _get_default_category_domain(self):
+ return [('parent_id', '=', False)]
+
+ merk_dagang = fields.Char(string='Merk Dagang')
+ is_pengajuan_tempo = fields.Boolean(string='Apakah anda memiliki Form Pengajuan Tempo?')
+ tempo_duration = fields.Many2one('account.payment.term', string='Durasi Tempo')
+ kredit_limit = fields.Char(string='Kredit Limit')
+ waktu_pengiriman = fields.Char(string='Waktu Pengiriman')
+ terhitung_sejak = fields.Selection([
+ ('1', 'Terima PO'),
+ ('2', 'Barang Dikirimkan'),
+ ('3', 'Tukar Faktur'),
+ ])
+
+ # syarat dagang
+ is_kembali_barang = fields.Char(string='Syarat Pengembalian Barang')
+ tenggat_waktu = fields.Char(string='Tenggat Waktu Perubahan Harga')
+ sertifikat_produk = fields.Char(string='Dokumen/Sertifikat yang Dimiliki Oleh Brand')
+ custom_sertifikat_produk = fields.Char(string='Dokumen/Sertifikat Lainnya')
+ tempo_garansi = fields.Selection([
+ ('1', '6 Bulan Garansi'),
+ ('2', '1 Tahun Garansi'),
+ ('3', '2 Tahun Garansi'),
+ ])
+ explain_garansi = fields.Char(string='Garansi Yang Dimaksudkan')
+ is_order_quantity = fields.Char(string='Apakah Memiliki Minimum Order Quantity (MOQ)')
+
+ # dokumen
+ file_npwp = fields.Many2one('ir.attachment', string="NPWP Perusahaan", tracking=3)
+ file_sppkp = fields.Many2one('ir.attachment', string="SPPKP Perusahaan", tracking=3)
+ file_dokumenKtpDirut = fields.Many2one('ir.attachment', string="KTP Dirut/Direktur", tracking=3)
+ file_kartuNama = fields.Many2one('ir.attachment', string="Kartu Nama", tracking=3)
+ file_suratPernyataan = fields.Many2one('ir.attachment', string="Surat Pernyataan Nomor Rekening", tracking=3)
+ file_fotoKantor = fields.Many2one('ir.attachment', string="Foto Gudang / Kantor Bagian Depan", tracking=3)
+ file_dataProduk = fields.Many2one('ir.attachment', string="Data Produk (Item Name, Gambar, Deskripsi)", tracking=3)
+ file_pricelist = fields.Many2one('ir.attachment', string="Pricelist", tracking=3)
+
+ @api.depends('name', 'name_merchant')
+ def name_get(self):
+ result = []
+ for record in self:
+ if record.name_merchant:
+ display_name = record.name_merchant
+ else:
+ display_name = "DETAIL FORM MERCHANT"
+ result.append((record.id, display_name))
+ return result \ No newline at end of file
diff --git a/indoteknik_custom/models/user_merchant_request.py b/indoteknik_custom/models/user_merchant_request.py
new file mode 100644
index 00000000..dd571cdc
--- /dev/null
+++ b/indoteknik_custom/models/user_merchant_request.py
@@ -0,0 +1,125 @@
+from odoo import models, fields, api, _
+from odoo.exceptions import UserError
+from odoo.http import request
+
+
+class RejectReasonWizardMerchant(models.TransientModel):
+ _name = 'reject.reason.wizard.merchant'
+ _description = 'Wizard for Reject Reason'
+
+ request_id = fields.Many2one('user.merchant.request', string='Request')
+ reason_reject = fields.Text(string='Reason for Rejection', required=True)
+
+ def confirm_reject(self):
+ merchant = self.request_id
+ if merchant:
+ merchant.write({'reason_reject': self.reason_reject})
+ merchant.state_merchant = 'reject'
+ return {'type': 'ir.actions.act_window_close'}
+
+
+class ConfirmApprovalWizardMerchant(models.TransientModel):
+ _name = 'confirm.approval.wizard.merchant'
+ _description = 'Wizard Konfirmasi Approval'
+
+ merchant_id = fields.Many2one('user.merchant.request', string='Merchant', required=True)
+
+ def confirm_approval(self):
+ merchant = self.merchant_id
+ if merchant.state_merchant == 'draft':
+ merchant.state_merchant = 'approved'
+
+
+class UserMerchantRequest(models.Model):
+ _name = 'user.merchant.request'
+ _inherit = ['mail.thread', 'mail.activity.mixin']
+ _rec_name = 'user_id'
+
+ user_id = fields.Many2one('res.partner', string='User')
+ merchant_id = fields.Many2one('user.form.merchant', string='Form Merchant')
+ user_company_id = fields.Many2one('res.partner', string='Company')
+ state_merchant = fields.Selection([
+ ('draft', 'Pengajuan Merchant'),
+ ('approved', 'Approved Merchant'),
+ ('reject', 'Rejected'),
+ ], string='Status', readonly=True, copy=False, index=True, track_visibility='onchange', default='draft')
+ reason_reject = fields.Char(string='Reaject Reason')
+
+ def button_approve(self):
+ for merchant in self:
+ return {
+ 'type': 'ir.actions.act_window',
+ 'name': 'Konfirmasi Approve',
+ 'res_model': 'confirm.approval.wizard.merchant',
+ 'view_mode': 'form',
+ 'target': 'new',
+ 'context': {
+ 'default_merchant_id': merchant.id,
+ }}
+
+ def button_reject(self):
+ return {
+ 'type': 'ir.actions.act_window',
+ 'name': _('Reject Reason'),
+ 'res_model': 'reject.reason.wizard.merchant',
+ 'view_mode': 'form',
+ 'target': 'new',
+ 'context': {'default_request_id': self.id},
+ }
+
+ def write(self, vals):
+ is_approve = True if self.state_merchant == 'approved' or vals.get('state_merchant') == 'approved' else False
+ if is_approve:
+ # Informasi Perusahaan
+ self.user_company_id.name_merchant = self.merchant_id.name_merchant
+ self.user_company_id.pejabat_name = self.merchant_id.pejabat_name
+ self.user_company_id.pic_merchant = self.merchant_id.pic_merchant
+ self.user_company_id.pic_position = self.merchant_id.pic_position
+ self.user_company_id.address_merchant = self.merchant_id.address
+ self.user_company_id.state_merchant = self.merchant_id.state
+ self.user_company_id.city_merchant = self.merchant_id.city
+ self.user_company_id.district_merchant = self.merchant_id.district
+ self.user_company_id.subDistrict_merchant = self.merchant_id.subDistrict
+ self.user_company_id.zip_merchant = self.merchant_id.zip
+ self.user_company_id.bank_name_merchant = self.merchant_id.bank_name
+ self.user_company_id.rekening_name_merchant = self.merchant_id.rekening_name
+ self.user_company_id.account_number_merchant = self.merchant_id.account_number
+ self.user_company_id.email_company_merchant = self.merchant_id.email_company
+ self.user_company_id.email_sales_merchant = self.merchant_id.email_sales
+ self.user_company_id.email_finance_merchant = self.merchant_id.email_finance
+ self.user_company_id.phone_merchant = self.merchant_id.phone
+ self.user_company_id.mobile_merchant = self.merchant_id.mobile
+ self.user_company_id.bisnis_type = self.merchant_id.bisnis_type
+ self.user_company_id.website_merchant = self.merchant_id.website
+ self.user_company_id.category_perusahaan = self.merchant_id.category_perusahaan
+
+ # Informasi Vendor
+ self.user_company_id.harga_tayang = self.merchant_id.harga_tayang
+ self.user_company_id.category_produk_ids_merchant = self.merchant_id.category_produk_ids
+ self.user_company_id.merk_dagang = self.merchant_id.merk_dagang
+ self.user_company_id.is_pengajuan_tempo = self.merchant_id.is_pengajuan_tempo
+ self.user_company_id.tempo_duration_merchant = self.merchant_id.tempo_duration
+ self.user_company_id.kredit_limit = self.merchant_id.kredit_limit
+ self.user_company_id.waktu_pengiriman = self.merchant_id.waktu_pengiriman
+ self.user_company_id.terhitung_sejak = self.merchant_id.terhitung_sejak
+
+ # Syarat Perdagangan
+ self.user_company_id.is_kembali_barang = self.merchant_id.is_kembali_barang
+ self.user_company_id.tenggat_waktu = self.merchant_id.tenggat_waktu
+ self.user_company_id.sertifikat_produk = self.merchant_id.sertifikat_produk
+ self.user_company_id.tempo_garansi = self.merchant_id.tempo_garansi
+ self.user_company_id.explain_garansi = self.merchant_id.explain_garansi
+ self.user_company_id.is_order_quantity = self.merchant_id.is_order_quantity
+
+ # Dokumen
+ self.user_company_id.file_npwp = self.merchant_id.file_npwp
+ self.user_company_id.file_sppkp = self.merchant_id.file_sppkp
+ self.user_company_id.file_dokumenKtpDirut = self.merchant_id.file_dokumenKtpDirut
+ self.user_company_id.file_kartuNama = self.merchant_id.file_kartuNama
+ self.user_company_id.file_suratPernyataan = self.merchant_id.file_suratPernyataan
+ self.user_company_id.file_fotoKantor = self.merchant_id.file_fotoKantor
+ self.user_company_id.file_dataProduk = self.merchant_id.file_dataProduk
+ self.user_company_id.file_pricelist = self.merchant_id.file_pricelist
+ self.user_company_id.description = self.merchant_id.description
+
+ return super(UserMerchantRequest, self).write(vals) \ No newline at end of file
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..565b0315 100644
--- a/indoteknik_custom/models/user_pengajuan_tempo_request.py
+++ b/indoteknik_custom/models/user_pengajuan_tempo_request.py
@@ -108,16 +108,18 @@ 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)
+ street_pengiriman = fields.Char(string="Alamat Pengiriman Barang", 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)
city_id_pengiriman = fields.Many2one('vit.kota', string='City', related='pengajuan_tempo_id.city_id_pengiriman', store=True, readonly=False)
district_id_pengiriman = fields.Many2one('vit.kecamatan', string='Kecamatan',related='pengajuan_tempo_id.district_id_pengiriman', store=True, readonly=False)
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)
+ street_invoice = fields.Char(string="Alamat Pengiriman Invoice", 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)
city_id_invoice = fields.Many2one('vit.kota', string='City', related='pengajuan_tempo_id.city_id_invoice', store=True, readonly=False)
district_id_invoice = fields.Many2one('vit.kecamatan', string='Kecamatan', related='pengajuan_tempo_id.district_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,
@@ -535,20 +548,7 @@ class UserPengajuanTempoRequest(models.Model):
('name', '=', contact_data['name'])
], limit=1)
- if existing_contact:
- # Pastikan tidak ada duplikasi nama dalam perusahaan yang sama
- duplicate_check = self.env['res.partner'].search([
- ('name', '=', contact_data['name']),
- ('id', '!=', existing_contact.id) # Hindari update yang menyebabkan duplikasi global
- ], limit=1)
-
- if not duplicate_check:
- # Perbarui hanya field yang tidak menyebabkan konflik
- update_data = {k: v for k, v in contact_data.items() if k != 'name'}
- existing_contact.write(update_data)
- else:
- raise UserError(f"Skipping update for {contact_data['name']} due to existing duplicate.")
- else:
+ if not existing_contact:
# Pastikan tidak ada partner lain dengan nama yang sama sebelum membuat baru
duplicate_check = self.env['res.partner'].search([
('name', '=', contact_data['name'])
@@ -583,6 +583,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 +596,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/models/voucher.py b/indoteknik_custom/models/voucher.py
index 101d4bcf..b213a039 100644
--- a/indoteknik_custom/models/voucher.py
+++ b/indoteknik_custom/models/voucher.py
@@ -11,43 +11,47 @@ class Voucher(models.Model):
name = fields.Char(string='Name')
image = fields.Binary(string='Image')
code = fields.Char(string='Code', help='Kode voucher yang akan berlaku untuk pengguna')
+ voucher_category = fields.Many2many('product.public.category', string='Category Voucher',
+ help='Kategori Produk yang dapat menggunakan voucher ini')
description = fields.Text(string='Description')
discount_amount = fields.Float(string='Discount Amount')
- discount_type = fields.Selection(string='Discount Type',
- selection=[
- ('percentage', 'Percentage'),
- ('fixed_price', 'Fixed Price'),
- ],
- help='Select the type of discount:\n'
- '- Percentage: Persentase dari total harga.\n'
- '- Fixed Price: Jumlah tetap yang dikurangi dari harga total.'
- )
- visibility = fields.Selection(string='Visibility',
- selection=[
- ('public', 'Public'),
- ('private', 'Private')
- ],
- help='Select the visibility:\n'
- '- Public: Ditampilkan kepada seluruh pengguna.\n'
- '- Private: Tidak ditampilkan kepada seluruh pengguna.'
- )
+ discount_type = fields.Selection(string='Discount Type',
+ selection=[
+ ('percentage', 'Percentage'),
+ ('fixed_price', 'Fixed Price'),
+ ],
+ help='Select the type of discount:\n'
+ '- Percentage: Persentase dari total harga.\n'
+ '- Fixed Price: Jumlah tetap yang dikurangi dari harga total.'
+ )
+ visibility = fields.Selection(string='Visibility',
+ selection=[
+ ('public', 'Public'),
+ ('private', 'Private')
+ ],
+ help='Select the visibility:\n'
+ '- Public: Ditampilkan kepada seluruh pengguna.\n'
+ '- Private: Tidak ditampilkan kepada seluruh pengguna.'
+ )
start_time = fields.Datetime(string='Start Time')
end_time = fields.Datetime(string='End Time')
- min_purchase_amount = fields.Integer(string='Min. Purchase Amount', help='Nominal minimum untuk dapat menggunakan voucher. Isi 0 jika tidak ada minimum purchase amount')
+ min_purchase_amount = fields.Integer(string='Min. Purchase Amount',
+ help='Nominal minimum untuk dapat menggunakan voucher. Isi 0 jika tidak ada minimum purchase amount')
max_discount_amount = fields.Integer(string='Max. Discount Amount', help='Max nominal terhadap persentase diskon')
order_ids = fields.One2many('sale.order', 'applied_voucher_id', string='Order')
limit = fields.Integer(
- string='Limit',
+ string='Limit',
default=0,
help='Batas penggunaan voucher keseluruhan. Isi dengan angka 0 untuk penggunaan tanpa batas'
)
limit_user = fields.Integer(
- string='Limit User',
+ string='Limit User',
default=0,
help='Batas penggunaan voucher per pengguna. Misalnya, jika diisi dengan angka 1, maka setiap pengguna hanya dapat menggunakan voucher ini satu kali. Isi dengan angka 0 untuk penggunaan tanpa batas'
)
manufacture_ids = fields.Many2many('x_manufactures', string='Brands', help='Voucher appplied only for brand')
- excl_pricelist_ids = fields.Many2many('product.pricelist', string='Excluded Pricelists', help='Hide voucher from selected exclude pricelist')
+ excl_pricelist_ids = fields.Many2many('product.pricelist', string='Excluded Pricelists',
+ help='Hide voucher from selected exclude pricelist')
voucher_line = fields.One2many('voucher.line', 'voucher_id', 'Voucher Line')
terms_conditions = fields.Html('Terms and Conditions')
apply_type = fields.Selection(string='Apply Type', default="all", selection=[
@@ -64,12 +68,40 @@ class Voucher(models.Model):
('person', "Account Individu"),
('company', "Account Company"),
])
+
+ def is_voucher_applicable(self, product_id):
+ if not self.voucher_category:
+ return True
+
+ public_categories = product_id.public_categ_ids
+
+ return bool(set(public_categories.ids) & set(self.voucher_category.ids))
+
+ def is_voucher_applicable_for_category(self, category):
+ if not self.voucher_category:
+ return True
+
+ if category.id in self.voucher_category.ids:
+ return True
+
+ category_path = []
+ current_cat = category
+ while current_cat:
+ category_path.append(current_cat.id)
+ current_cat = current_cat.parent_id
+
+ for voucher_cat in self.voucher_category:
+ if voucher_cat.id in category_path:
+ return True
+
+ return False
+
@api.constrains('description')
def _check_description_length(self):
for record in self:
if record.description and len(record.description) > 120:
raise ValidationError('Deskripsi tidak boleh lebih dari 120 karakter')
-
+
@api.constrains('limit', 'limit_user')
def _check_limit(self):
for rec in self:
@@ -87,7 +119,7 @@ class Voucher(models.Model):
def res_format(self):
datas = [voucher.format() for voucher in self]
return datas
-
+
def format(self):
ir_attachment = self.env['ir.attachment']
data = {
@@ -100,7 +132,7 @@ class Voucher(models.Model):
'remaining_time': self._res_remaining_time(),
}
return data
-
+
def _res_remaining_time(self):
seconds = self._get_remaining_time()
remaining_time = timedelta(seconds=seconds)
@@ -116,14 +148,31 @@ class Voucher(models.Model):
time = minutes
unit = 'menit'
return f'{time} {unit}'
-
+
def _get_remaining_time(self):
calculate_time = self.end_time - datetime.now()
return round(calculate_time.total_seconds())
-
+
def filter_order_line(self, order_line):
+ # import logging
+ # _logger = logging.getLogger(__name__)
+
voucher_manufacture_ids = self.collect_manufacture_ids()
results = []
+
+ if self.voucher_category and len(order_line) > 0:
+ for line in order_line:
+ category_applicable = False
+ for category in line['product_id'].public_categ_ids:
+ if self.is_voucher_applicable_for_category(category):
+ category_applicable = True
+ break
+
+ if not category_applicable:
+ # _logger.info("Cart contains product %s with non-applicable category - voucher %s cannot be used",
+ # line['product_id'].name, self.code)
+ return []
+
for line in order_line:
manufacture_id = line['product_id'].x_manufacture.id or None
if self.apply_type == 'brand' and manufacture_id not in voucher_manufacture_ids:
@@ -132,35 +181,36 @@ class Voucher(models.Model):
product_flashsale = line['product_id']._get_active_flash_sale()
if len(product_flashsale) > 0:
continue
-
+
results.append(line)
-
+
return results
-
+
def calc_total_order_line(self, order_line):
- result = { 'all': 0, 'brand': {} }
+ result = {'all': 0, 'brand': {}}
for line in order_line:
manufacture_id = line['product_id'].x_manufacture.id or None
manufacture_total = result['brand'].get(manufacture_id, 0)
result['brand'][manufacture_id] = manufacture_total + line['subtotal']
result['all'] += line['subtotal']
-
+
return result
-
+
def calc_discount_amount(self, total):
- result = { 'all': 0, 'brand': {} }
+ result = {'all': 0, 'brand': {}}
if self.apply_type in ['all', 'shipping']:
if total['all'] < self.min_purchase_amount:
return result
-
+
if self.discount_type == 'percentage':
decimal_discount = self.discount_amount / 100
discount_all = total['all'] * decimal_discount
- result['all'] = min(discount_all, self.max_discount_amount) if self.max_discount_amount > 0 else discount_all
+ result['all'] = min(discount_all,
+ self.max_discount_amount) if self.max_discount_amount > 0 else discount_all
else:
result['all'] = min(self.discount_amount, total['all'])
-
+
return result
for line in self.voucher_line:
@@ -173,95 +223,141 @@ class Voucher(models.Model):
elif line.discount_type == 'percentage':
decimal_discount = line.discount_amount / 100
discount_brand = total_brand * decimal_discount
- discount_brand = min(discount_brand, line.max_discount_amount) if line.max_discount_amount > 0 else discount_brand
+ discount_brand = min(discount_brand,
+ line.max_discount_amount) if line.max_discount_amount > 0 else discount_brand
else:
discount_brand = min(line.discount_amount, total_brand)
-
+
result['brand'][manufacture_id] = round(discount_brand, 2)
result['all'] += discount_brand
-
+
result['all'] = round(result['all'], 2)
return result
def apply(self, order_line):
- order_line = self.filter_order_line(order_line)
- amount_total = self.calc_total_order_line(order_line)
+
+ filtered_order_line = self.filter_order_line(order_line)
+
+ amount_total = self.calc_total_order_line(filtered_order_line)
+
discount = self.calc_discount_amount(amount_total)
+
return {
'discount': discount,
'total': amount_total,
'type': self.apply_type,
- 'valid_order': order_line
+ 'valid_order': filtered_order_line,
}
-
+
def collect_manufacture_ids(self):
return [x.manufacture_id.id for x in self.voucher_line]
-
+
def calculate_discount(self, price):
if price < self.min_purchase_amount:
return 0
-
+
if self.discount_type == 'fixed_price':
return self.discount_amount
-
+
if self.discount_type == 'percentage':
discount = price * self.discount_amount / 100
max_disc = self.max_discount_amount
return discount if max_disc == 0 else min(discount, max_disc)
-
+
return 0
-
+
def get_active_voucher(self, domain):
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
domain += [
('start_time', '<=', current_time),
('end_time', '>=', current_time),
]
- vouchers = self.search(domain, order='min_purchase_amount ASC')
+ vouchers = self.search(domain, order='min_purchase_amount ASC')
return vouchers
-
+
def generate_tnc(self):
+ def format_currency(amount):
+ formatted_number = '{:,.0f}'.format(amount).replace(',', '.')
+ return f'Rp{formatted_number}'
+
tnc = []
tnc.append('<ol>')
- tnc.append('<li>Voucher hanya berlaku apabila pembelian Pengguna sudah memenuhi syarat dan ketentuan yang tertera pada voucher</li>')
+ tnc.append(
+ '<li>Voucher hanya berlaku apabila pembelian Pengguna sudah memenuhi syarat dan ketentuan yang tertera pada voucher</li>')
tnc.append(f'<li>Voucher berlaku {self._res_remaining_time()} lagi</li>')
tnc.append(f'<li>Voucher tidak bisa digunakan apabila terdapat produk flash sale</li>')
- if len(self.voucher_line) > 0:
- brand_names = ', '.join([x.manufacture_id.x_name or '' for x in self.voucher_line])
- tnc.append(f'<li>Voucher berlaku untuk produk dari brand {brand_names}</li>')
- tnc.append(self.generate_detail_tnc())
+
+ if self.apply_type == 'brand':
+ tnc.append(f'<li>Voucher berlaku untuk produk dari brand terpilih</li>')
+ tnc.append(
+ f'<li>Nominal potongan produk yang bisa didapatkan hingga 10 Juta dengan minimum pembelian 10 Ribu.</li>')
+ elif self.apply_type == 'all' or self.apply_type == 'shipping':
+ if self.voucher_category:
+ category_names = ', '.join([cat.name for cat in self.voucher_category])
+ tnc.append(
+ f'<li>Voucher hanya berlaku untuk produk dalam kategori {category_names} dan sub-kategorinya</li>')
+ tnc.append(
+ f'<li>Voucher tidak dapat digunakan jika ada produk di keranjang yang tidak termasuk dalam kategori tersebut</li>')
+
+ if self.discount_type == 'percentage' and self.apply_type != 'brand':
+ tnc.append(
+ f'<li>Nominal potongan produk yang bisa didapatkan sebesar {self.max_discount_amount}% dengan minimum pembelian {self.min_purchase_amount}</li>')
+ elif self.discount_type == 'percentage' and self.apply_type != 'brand':
+ tnc.append(
+ f'<li>Nominal potongan produk yang bisa didapatkan sebesar {format_currency(self.discount_amount)} dengan minimum pembelian {format_currency(self.min_purchase_amount)}</li>')
+
tnc.append('</ol>')
-
- return ' '.join(tnc)
-
- def generate_detail_tnc(self):
- def format_currency(amount):
- formatted_number = '{:,.0f}'.format(amount).replace(',', '.')
- return f'Rp{formatted_number}'
-
- tnc = []
- if self.apply_type == 'all':
- tnc.append('<li>')
- tnc.append('Nominal potongan yang bisa didapatkan sebesar')
- tnc.append(f'{self.discount_amount}%' if self.discount_type == 'percentage' else format_currency(self.discount_amount))
-
- if self.discount_type == 'percentage' and self.max_discount_amount > 0:
- tnc.append(f'hingga {format_currency(self.max_discount_amount)}')
-
- tnc.append(f'dengan minimum pembelian {format_currency(self.min_purchase_amount)}' if self.min_purchase_amount > 0 else 'tanpa minimum pembelian')
- tnc.append('</li>')
- else:
- for line in self.voucher_line:
- line_tnc = []
- line_tnc.append(f'Nominal potongan produk {line.manufacture_id.x_name} yang bisa didapatkan sebesar')
- line_tnc.append(f'{line.discount_amount}%' if line.discount_type == 'percentage' else format_currency(line.discount_amount))
-
- if line.discount_type == 'percentage' and line.max_discount_amount > 0:
- line_tnc.append(f'hingga {format_currency(line.max_discount_amount)}')
-
- line_tnc.append(f'dengan minimum pembelian {format_currency(line.min_purchase_amount)}' if line.min_purchase_amount > 0 else 'tanpa minimum pembelian')
- line_tnc = ' '.join(line_tnc)
- tnc.append(f'<li>{line_tnc}</li>')
+ # tnc.append(self.generate_detail_tnc())
return ' '.join(tnc)
+ # def generate_detail_tnc(self):
+ # def format_currency(amount):
+ # formatted_number = '{:,.0f}'.format(amount).replace(',', '.')
+ # return f'Rp{formatted_number}'
+ #
+ # tnc = []
+ # if self.apply_type == 'all':
+ # tnc.append('<li>')
+ # tnc.append('Nominal potongan yang bisa didapatkan sebesar')
+ # tnc.append(f'{self.discount_amount}%' if self.discount_type == 'percentage' else format_currency(
+ # self.discount_amount))
+ #
+ # if self.discount_type == 'percentage' and self.max_discount_amount > 0:
+ # tnc.append(f'hingga {format_currency(self.max_discount_amount)}')
+ #
+ # tnc.append(
+ # f'dengan minimum pembelian {format_currency(self.min_purchase_amount)}' if self.min_purchase_amount > 0 else 'tanpa minimum pembelian')
+ # tnc.append('</li>')
+ # else:
+ # for line in self.voucher_line:
+ # line_tnc = []
+ # line_tnc.append(f'Nominal potongan produk {line.manufacture_id.x_name} yang bisa didapatkan sebesar')
+ # line_tnc.append(f'{line.discount_amount}%' if line.discount_type == 'percentage' else format_currency(
+ # line.discount_amount))
+ #
+ # if line.discount_type == 'percentage' and line.max_discount_amount > 0:
+ # line_tnc.append(f'hingga {format_currency(line.max_discount_amount)}')
+ #
+ # line_tnc.append(
+ # f'dengan minimum pembelian {format_currency(line.min_purchase_amount)}' if line.min_purchase_amount > 0 else 'tanpa minimum pembelian')
+ # line_tnc = ' '.join(line_tnc)
+ # tnc.append(f'<li>{line_tnc}</li>')
+ # return ' '.join(tnc)
+
+ # copy semua data kalau diduplicate
+ def copy(self, default=None):
+ default = dict(default or {})
+ voucher_lines = []
+
+ for line in self.voucher_line:
+ voucher_lines.append((0, 0, {
+ 'manufacture_id': line.manufacture_id.id,
+ 'discount_amount': line.discount_amount,
+ 'discount_type': line.discount_type,
+ 'min_purchase_amount': line.min_purchase_amount,
+ 'max_discount_amount': line.max_discount_amount,
+ }))
+
+ default['voucher_line'] = voucher_lines
+ return super(Voucher, self).copy(default)
diff --git a/indoteknik_custom/models/website_user_cart.py b/indoteknik_custom/models/website_user_cart.py
index 44393cf1..a6d08949 100644
--- a/indoteknik_custom/models/website_user_cart.py
+++ b/indoteknik_custom/models/website_user_cart.py
@@ -1,10 +1,11 @@
from odoo import fields, models, api
from datetime import datetime, timedelta
+
class WebsiteUserCart(models.Model):
_name = 'website.user.cart'
_rec_name = 'user_id'
-
+
user_id = fields.Many2one('res.users', string='User')
product_id = fields.Many2one('product.product', string='Product')
program_line_id = fields.Many2one('promotion.program.line', string='Program', help="Apply program")
@@ -18,7 +19,8 @@ class WebsiteUserCart(models.Model):
is_reminder = fields.Boolean(string='Reminder?')
phone_user = fields.Char(string='Phone', related='user_id.mobile')
price = fields.Float(string='Price', compute='_compute_price')
- program_product_id = fields.Many2one('product.product', string='Program Products', compute='_compute_program_product_ids')
+ program_product_id = fields.Many2one('product.product', string='Program Products',
+ compute='_compute_program_product_ids')
@api.depends('program_line_id')
def _compute_program_product_ids(self):
@@ -55,6 +57,12 @@ class WebsiteUserCart(models.Model):
product = self.product_id.v2_api_single_response(self.product_id)
res.update(product)
+ # Add category information
+ res['categories'] = [{
+ 'id': cat.id,
+ 'name': cat.name
+ } for cat in self.product_id.public_categ_ids]
+
# Check if the product's inventory location is in ID 57 or 83
target_locations = [57, 83]
stock_quant = self.env['stock.quant'].search([
@@ -90,7 +98,14 @@ class WebsiteUserCart(models.Model):
def get_products(self):
products = [x.get_product() for x in self]
-
+
+ for i, cart_item in enumerate(self):
+ if cart_item.product_id and i < len(products):
+ products[i]['categories'] = [{
+ 'id': cat.id,
+ 'name': cat.name
+ } for cat in cart_item.product_id.public_categ_ids]
+
return products
def get_product_by_user(self, user_id, selected=False, source=False):
@@ -121,10 +136,10 @@ class WebsiteUserCart(models.Model):
products = products_active.get_products()
return products
-
+
def get_user_checkout(self, user_id, voucher=False, voucher_shipping=False, source=False):
products = self.get_product_by_user(user_id=user_id, selected=True, source=source)
-
+
total_purchase = 0
total_discount = 0
for product in products:
@@ -132,9 +147,9 @@ class WebsiteUserCart(models.Model):
price = product['package_price'] * product['quantity']
else:
price = product['price']['price'] * product['quantity']
-
+
discount_price = price - product['price']['price_discount'] * product['quantity']
-
+
total_purchase += price
total_discount += discount_price
@@ -142,7 +157,7 @@ class WebsiteUserCart(models.Model):
discount_voucher = 0
discount_voucher_shipping = 0
order_line = []
-
+
if voucher or voucher_shipping:
for product in products:
if product['cart_type'] == 'promotion': continue
@@ -153,16 +168,16 @@ class WebsiteUserCart(models.Model):
'qty': product['quantity'],
'subtotal': product['subtotal']
})
-
+
if voucher:
voucher_info = voucher.apply(order_line)
discount_voucher = voucher_info['discount']['all']
subtotal -= discount_voucher
-
+
if voucher_shipping:
voucher_shipping_info = voucher_shipping.apply(order_line)
- discount_voucher_shipping = voucher_shipping_info['discount']['all']
-
+ discount_voucher_shipping = voucher_shipping_info['discount']['all']
+
tax = round(subtotal * 0.11)
grand_total = subtotal + tax
total_weight = sum(x['weight'] * x['quantity'] for x in products)
@@ -179,28 +194,31 @@ class WebsiteUserCart(models.Model):
'kg': total_weight,
'g': total_weight * 1000
},
- 'has_product_without_weight': any(not product.get('weight') or product.get('weight') == 0 for product in products),
+ 'has_product_without_weight': any(
+ not product.get('weight') or product.get('weight') == 0 for product in products),
'products': products
}
return result
-
+
def action_mail_reminder_to_checkout(self, limit=200):
user_ids = self.search([('is_reminder', '=', False)]).mapped('user_id')[:limit]
-
+
for user in user_ids:
latest_cart = self.search([('user_id', '=', user.id)], order='create_date desc', limit=1)
-
+
carts_to_remind = self.search([('user_id', '=', user.id)])
-
+
if latest_cart and not latest_cart.is_reminder:
for cart in carts_to_remind:
check = cart.check_product_flashsale(cart.product_id.id)
- if not cart.program_line_id and cart.product_id.default_code and not 'BOM' in cart.product_id.default_code and check['is_flashsale'] == False:
+ if not cart.program_line_id and cart.product_id.default_code and not 'BOM' in cart.product_id.default_code and \
+ check['is_flashsale'] == False:
cart.is_selected = True
- if cart.program_line_id or check['is_flashsale'] or cart.product_id.default_code and 'BOM' in cart.product_id.default_code:
+ if cart.program_line_id or check[
+ 'is_flashsale'] or cart.product_id.default_code and 'BOM' in cart.product_id.default_code:
cart.is_selected = False
cart.is_reminder = True
-
+
template = self.env.ref('indoteknik_custom.mail_template_user_cart_reminder_to_checkout')
template.send_mail(latest_cart.id, force_send=True)
@@ -234,8 +252,9 @@ class WebsiteUserCart(models.Model):
break
product_discount = subtotal_promo if cart.program_line_id or check['is_flashsale'] else subtotal
- total_discount += product_discount
- if check['is_flashsale'] == False and cart.product_id.default_code and not 'BOM' in cart.product_id.default_code:
+ total_discount += product_discount
+ if check[
+ 'is_flashsale'] == False and cart.product_id.default_code and not 'BOM' in cart.product_id.default_code:
voucher_product = subtotal * (discount_amount / 100.0)
total_voucher += voucher_product
@@ -253,14 +272,15 @@ class WebsiteUserCart(models.Model):
def check_product_flashsale(self, product_id):
product = product_id
current_time = datetime.utcnow()
- found_product = self.env['product.pricelist.item'].search([('product_id', '=', product_id), ('pricelist_id.is_flash_sale', '=', True)])
+ found_product = self.env['product.pricelist.item'].search(
+ [('product_id', '=', product_id), ('pricelist_id.is_flash_sale', '=', True)])
if found_product:
for found in found_product:
pricelist_flashsale = found.pricelist_id
if pricelist_flashsale.start_date <= current_time <= pricelist_flashsale.end_date:
- return {
+ return {
'is_flashsale': True,
'price': found.fixed_price
}
@@ -269,10 +289,9 @@ class WebsiteUserCart(models.Model):
'is_flashsale': False
}
- return {
+ return {
'is_flashsale': False
}
-
# if found_product:
# start_date = found_product.pricelist_id.start_date
@@ -291,26 +310,26 @@ class WebsiteUserCart(models.Model):
# return {
# 'is_flashsale': False
# }
-
+
def get_data_promo(self, program_line_id):
program_line_product = self.env['promotion.product'].search([
('program_line_id', '=', program_line_id)
- ])
-
+ ])
+
program_free_product = self.env['promotion.free_product'].search([
('program_line_id', '=', program_line_id)
- ])
+ ])
return program_line_product, program_free_product
-
+
def get_weight_product(self, program_line_id):
program_line_product = self.env['promotion.product'].search([
('program_line_id', '=', program_line_id)
- ])
-
+ ])
+
program_free_product = self.env['promotion.free_product'].search([
('program_line_id', '=', program_line_id)
- ])
-
+ ])
+
real_weight = 0.0
if program_line_product:
for product in program_line_product:
@@ -321,16 +340,16 @@ class WebsiteUserCart(models.Model):
real_weight += product.product_id.weight
return real_weight
-
+
def get_price_coret(self, program_line_id):
program_line_product = self.env['promotion.product'].search([
('program_line_id', '=', program_line_id)
- ])
-
+ ])
+
program_free_product = self.env['promotion.free_product'].search([
('program_line_id', '=', program_line_id)
- ])
-
+ ])
+
price_coret = 0.0
for product in program_line_product:
price = self.get_price_website(product.product_id.id)
@@ -340,20 +359,22 @@ class WebsiteUserCart(models.Model):
price = self.get_price_website(product.product_id.id)
price_coret += price['price'] * product.qty
- return price_coret
-
+ return price_coret
+
def get_price_website(self, product_id):
- price_website = self.env['product.pricelist.item'].search([('product_id', '=', product_id), ('pricelist_id', '=', 17022)], limit=1)
-
- price_tier = self.env['product.pricelist.item'].search([('product_id', '=', product_id), ('pricelist_id', '=', 17023)], limit=1)
-
+ price_website = self.env['product.pricelist.item'].search(
+ [('product_id', '=', product_id), ('pricelist_id', '=', 17022)], limit=1)
+
+ price_tier = self.env['product.pricelist.item'].search(
+ [('product_id', '=', product_id), ('pricelist_id', '=', 17023)], limit=1)
+
fixed_price = price_website.fixed_price if price_website else 0.0
discount = price_tier.price_discount if price_tier else 0.0
-
+
discounted_price = fixed_price - (fixed_price * discount / 100)
-
+
final_price = discounted_price / 1.11
-
+
return {
'price': final_price,
'web_price': discounted_price
@@ -365,4 +386,4 @@ class WebsiteUserCart(models.Model):
def format_currency(self, number):
number = int(number)
- return "{:,}".format(number).replace(',', '.') \ No newline at end of file
+ return "{:,}".format(number).replace(',', '.')
diff --git a/indoteknik_custom/models/x_banner_banner.py b/indoteknik_custom/models/x_banner_banner.py
index 810bdf39..16d54b02 100755
--- a/indoteknik_custom/models/x_banner_banner.py
+++ b/indoteknik_custom/models/x_banner_banner.py
@@ -25,4 +25,5 @@ class XBannerBanner(models.Model):
('4', '4')
], string='Group by Week')
x_headline_banner = fields.Text(string="Headline Banner")
- x_description_banner = fields.Text(string="Description Banner") \ No newline at end of file
+ x_description_banner = fields.Text(string="Description Banner")
+ x_keyword_banner = fields.Text(string="Keyword Banner") \ 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..28fc760d 100755
--- a/indoteknik_custom/security/ir.model.access.csv
+++ b/indoteknik_custom/security/ir.model.access.csv
@@ -138,6 +138,7 @@ access_shipment_group,access.shipment.group,model_shipment_group,,1,1,1,1
access_shipment_group_line,access.shipment.group.line,model_shipment_group_line,,1,1,1,1
access_sales_order_reject,access.sales.order.reject,model_sales_order_reject,,1,1,1,1
access_approval_date_doc,access.approval.date.doc,model_approval_date_doc,,1,1,1,1
+access_approval_invoice_date,access.approval.invoice.date,model_approval_invoice_date,,1,1,1,1
access_account_tax,access.account.tax,model_account_tax,,1,1,1,1
access_approval_unreserve,access.approval.unreserve,model_approval_unreserve,,1,1,1,1
access_approval_unreserve_line,access.approval.unreserve.line,model_approval_unreserve_line,,1,1,1,1
@@ -151,18 +152,36 @@ 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_check_bom_product,access.check.bom.product,model_check_bom_product,,1,1,1,1
+access_check_koli,access.check.koli,model_check_koli,,1,1,1,1
+access_scan_koli,access.scan.koli,model_scan_koli,,1,1,1,1
+access_konfirm_koli,access.konfirm.koli,model_konfirm_koli,,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_sales_order_koli,access.sales.order.koli,model_sales_order_koli,,1,1,1,1
+access_stock_backorder_confirmation,access.stock.backorder.confirmation,model_stock_backorder_confirmation,,1,1,1,1
+access_warning_modal_wizard,access.warning.modal.wizard,model_warning_modal_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_user_form_merchant,access.user.form.merchant,model_user_form_merchant,,1,1,1,1
+access_user_merchant_request,access.user.merchant.request,model_user_merchant_request,,1,1,1,1
+access_reject_reason_wizard_merchant,reject.reason.wizard.merchant,model_reject_reason_wizard_merchant,,1,1,1,0
+access_confirm_approval_wizard_merchant,confirm.approval.wizard.merchant,model_confirm_approval_wizard_merchant,,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_reject_reason_commision,reject.reason.commision,model_reject_reason_commision,,1,1,1,0
+access_shipping_option,shipping.option,model_shipping_option,,1,1,1,1
+access_production_purchase_match,access.production.purchase.match,model_production_purchase_match,,1,1,1,1
+access_image_carousel,access.image.carousel,model_image_carousel,,1,1,1,1
+access_v_sale_notin_matchpo,access.v.sale.notin.matchpo,model_v_sale_notin_matchpo,,1,1,1,1
diff --git a/indoteknik_custom/views/account_move.xml b/indoteknik_custom/views/account_move.xml
index 17263c3a..46737a40 100644
--- a/indoteknik_custom/views/account_move.xml
+++ b/indoteknik_custom/views/account_move.xml
@@ -92,6 +92,7 @@
<field name="is_efaktur_exported" optional="hide"/>
<field name="invoice_day_to_due" attrs="{'invisible': [['payment_state', 'in', ('paid', 'in_payment', 'reversed')]]}" optional="hide"/>
<field name="new_invoice_day_to_due" attrs="{'invisible': [['payment_state', 'in', ('paid', 'in_payment', 'reversed')]]}" optional="hide"/>
+ <field name="length_of_payment" optional="hide"/>
<field name="mark_upload_efaktur" optional="hide" widget="badge"
decoration-danger="mark_upload_efaktur == 'belum_upload'"
decoration-success="mark_upload_efaktur == 'sudah_upload'" />
diff --git a/indoteknik_custom/views/approval_invoice_date.xml b/indoteknik_custom/views/approval_invoice_date.xml
new file mode 100644
index 00000000..31f346e7
--- /dev/null
+++ b/indoteknik_custom/views/approval_invoice_date.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<odoo>
+ <record id="approval_invoice_date_tree" model="ir.ui.view">
+ <field name="name">approval.invoice.date.tree</field>
+ <field name="model">approval.invoice.date</field>
+ <field name="arch" type="xml">
+ <tree>
+ <field name="number"/>
+ <field name="picking_id"/>
+ <field name="partner_id"/>
+ <field name="sale_id"/>
+ <field name="date_doc_do"/>
+ <field name="date_invoice"/>
+ <field name="state" widget="badge" decoration-danger="state == 'cancel'"
+ decoration-success="state == 'done'"
+ decoration-info="state == 'draft'"/>
+ <field name="approve_date"/>
+ <field name="approve_by"/>
+ <field name="create_uid"/>
+ </tree>
+ </field>
+ </record>
+
+ <record id="approval_invoice_date_form" model="ir.ui.view">
+ <field name="name">approval.invoice.date.form</field>
+ <field name="model">approval.invoice.date</field>
+ <field name="arch" type="xml">
+ <form>
+ <header>
+ <button name="button_approve"
+ string="Approve"
+ type="object"
+ attrs="{'invisible': [('state', '=', 'done')]}"
+ />
+ <button name="button_cancel"
+ string="Cancel"
+ type="object"
+ attrs="{'invisible': [('state', '=', 'cancel')]}"
+ />
+ <field name="state" widget="statusbar"
+ statusbar_visible="draft,cancel,done"
+ statusbar_colors='{"cancel":"red", "done":"green"}'/>
+ </header>
+ <sheet string="Approval Invoice Date">
+ <group>
+ <group>
+ <field name="number"/>
+ <field name="picking_id"/>
+ <field name="partner_id"/>
+ <field name="sale_id"/>
+ <field name="move_id"/>
+ <field name="date_doc_do"/>
+ <field name="date_invoice"/>
+ <field name="approve_date"/>
+ <field name="approve_by"/>
+ <field name="create_uid"/>
+ <field name="note" attrs="{'invisible': [('state', '!=', 'cancel')]}"/>
+ </group>
+ </group>
+ </sheet>
+ </form>
+ </field>
+ </record>
+
+ <record id="view_approval_invoice_date_filter" model="ir.ui.view">
+ <field name="name">approval.invoice.date.list.select</field>
+ <field name="model">approval.invoice.date</field>
+ <field name="priority" eval="15"/>
+ <field name="arch" type="xml">
+ <search string="Search Approval Invoice Date">
+ <field name="number"/>
+ <field name="partner_id"/>
+ <field name="picking_id"/>
+ <field name="sale_id"/>
+ </search>
+ </field>
+ </record>
+
+ <record id="approval_invoice_date_action" model="ir.actions.act_window">
+ <field name="name">Approval Invoice Date</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">approval.invoice.date</field>
+ <field name="view_mode">tree,form</field>
+ </record>
+
+ <menuitem id="menu_approval_invoice_date" name="Approval Invoice Date"
+ parent="account.menu_finance_receivables"
+ action="approval_invoice_date_action"
+ sequence="100"
+ />
+
+</odoo> \ No newline at end of file
diff --git a/indoteknik_custom/views/barcoding_product.xml b/indoteknik_custom/views/barcoding_product.xml
index c7473d39..55876580 100644
--- a/indoteknik_custom/views/barcoding_product.xml
+++ b/indoteknik_custom/views/barcoding_product.xml
@@ -34,12 +34,13 @@
<group>
<field name="product_id" required="1"/>
<field name="type" required="1"/>
- <field name="quantity" attrs="{'invisible': [['type', 'in', ('barcoding')]], 'required': [['type', 'not in', ('barcoding')]]}"/>
+ <field name="quantity" attrs="{'invisible': [['type', 'in', ('barcoding','barcoding_box')]], 'required': [['type', 'not in', ('barcoding')]]}"/>
<field name="barcode" attrs="{'invisible': [['type', 'in', ('print')]], 'required': [['type', 'not in', ('print')]]}"/>
+ <field name="qty_pcs_box" attrs="{'invisible': [['type', 'in', ('print','barcoding')]], 'required': [['type', 'not in', ('print', 'barcoding')]]}"/>
</group>
</group>
<notebook>
- <page string="Line" attrs="{'invisible': [['type', 'in', ('barcoding')]]}">
+ <page string="Line" attrs="{'invisible': [['type', 'in', ('barcoding','barcoding_box')]]}">
<field name="barcoding_product_line"/>
</page>
</notebook>
diff --git a/indoteknik_custom/views/customer_commision.xml b/indoteknik_custom/views/customer_commision.xml
index 51172b1c..37df16ff 100644
--- a/indoteknik_custom/views/customer_commision.xml
+++ b/indoteknik_custom/views/customer_commision.xml
@@ -11,12 +11,15 @@
<field name="partner_ids" widget="many2many_tags"/>
<field name="commision_percent"/>
<field name="commision_amt" readonly="1"/>
- <field name="status" readonly="1"/>
+ <field name="status" readonly="1" decoration-success="status == 'approved'" widget="badge"
+ optional="show"/>
<field name="payment_status" readonly="1"
- decoration-success="payment_status == 'payment'"
- decoration-danger="payment_status == 'pending'"
- widget="badge"/>
+ decoration-success="payment_status == 'payment'"
+ decoration-danger="payment_status == 'pending'"
+ widget="badge"/>
<field name="brand_ids" widget="many2many_tags"/>
+ <field name="grouped_so_number" readonly="1" optional="hide"/>
+ <field name="grouped_invoice_number" readonly="1" optional="hide"/>
</tree>
</field>
</record>
@@ -28,10 +31,12 @@
<tree editable="top" create="false">
<field name="partner_id" readonly="1"/>
<field name="invoice_id" readonly="1"/>
+ <field name="sale_order_id" readonly="1"/>
<field name="state" readonly="1"/>
<field name="product_id" readonly="1" optional="hide"/>
<field name="dpp" readonly="1"/>
<field name="total_percent_margin" readonly="1"/>
+ <field name="total_margin_excl_third_party" readonly="1"/>
<field name="tax" readonly="1" optional="hide"/>
<field name="total" readonly="1" optional="hide"/>
</tree>
@@ -43,65 +48,112 @@
<field name="model">customer.commision</field>
<field name="arch" type="xml">
<form>
+ <!-- attrs="{'invisible': [('status', 'in', ['draft','pengajuan1','pengajuan2','pengajuan3','pengajuan4'])]}"-->
<header>
- <button name="action_confirm_customer_commision"
- string="Confirm" type="object"
- options="{}"/>
+ <button name="action_confirm_customer_commision"
+ string="Confirm" type="object"
+ attrs="{'invisible': [('status', 'in', ['approved','reject'])]}"
+ options="{}"/>
+ <button name="action_reject"
+ string="Reject"
+ attrs="{'invisible': [('status', 'in', ['approved','reject'])]}"
+ type="object"/>
+ <button name="button_draft"
+ string="Reset to Draft"
+ attrs="{'invisible': [('status', '!=', 'reject')]}"
+ type="object"/>
<button name="action_confirm_customer_payment"
- string="Konfirmasi Pembayaran" type="object"
- options="{}"
- attrs="{'invisible': [('payment_status', '==', 'payment')], 'readonly': [('payment_status', '=', 'payment')]}"/>
+ string="Konfirmasi Pembayaran" type="object"
+ options="{}"
+ attrs="{'invisible': [('payment_status', '==', 'payment')], 'readonly': [('payment_status', '=', 'payment')]}"/>
+ <field name="status" widget="statusbar"
+ statusbar_visible="draft,pengajuan1,pengajuan2,pengajuan3,pengajuan4,approved"
+ statusbar_colors='{"reject":"red"}'/>
</header>
<sheet string="Customer Commision">
- <div class="oe_button_box" name="button_box"/>
+ <div class="oe_button_box" name="button_box"/>
+ <group>
<group>
+ <field name="number"/>
+ <field name="date_from"/>
+ <field name="partner_ids" widget="many2many_tags"/>
+ <field name="description"/>
+ <field name="commision_percent"/>
+ <field name="commision_amt"/>
+ <field name="commision_amt_text"/>
+ <field name="grouped_so_number" readonly="1"/>
+ <field name="grouped_invoice_number" readonly="1"/>
+ <field name="approved_by" readonly="1"/>
+ </group>
+ <group>
+ <div>
+ <button name="generate_customer_commision"
+ string="Generate Line"
+ type="object"
+ class="mr-2 oe_highlight"
+ />
+ </div>
+ <field name="date_to"/>
+ <field name="sales_id"/>
+ <field name="commision_type"/>
+ <field name="brand_ids" widget="many2many_tags"/>
+ <field name="notification" readonly="1"/>
+ <!-- <field name="status" readonly="1"/>-->
+ <field name="payment_status" readonly="1"/>
+ <field name="total_dpp"/>
+ </group>
+ </group>
+ <notebook>
+ <page string="Lines">
+ <field name="commision_lines"/>
+ </page>
+ <page string="Other Info" name="customer_commision_info">
<group>
- <field name="number"/>
- <field name="date_from"/>
- <field name="partner_ids" widget="many2many_tags"/>
- <field name="description"/>
- <field name="commision_percent"/>
- <field name="commision_amt"/>
+ <field name="bank_name"/>
+ <field name="account_name"/>
+ <field name="bank_account"/>
+ <field name="note_transfer"/>
</group>
+ </page>
+ <page string="Finance Notes">
<group>
- <div>
- <button name="generate_customer_commision"
- string="Generate Line"
- type="object"
- class="mr-2 oe_highlight"
- />
- </div>
- <field name="date_to"/>
- <field name="commision_type"/>
- <field name="brand_ids" widget="many2many_tags"/>
- <field name="notification" readonly="1"/>
- <field name="status" readonly="1"/>
- <field name="payment_status" readonly="1" />
- <field name="total_dpp"/>
+ <field name="note_finnance"/>
</group>
- </group>
- <notebook>
- <page string="Lines">
- <field name="commision_lines"/>
- </page>
- <page string="Other Info" name="customer_commision_info">
- <group>
- <field name="bank_name"/>
- <field name="account_name"/>
- <field name="bank_account"/>
- <field name="note_transfer"/>
- </group>
- </page>
- </notebook>
- </sheet>
- <div class="oe_chatter">
- <field name="message_follower_ids" widget="mail_followers"/>
- <field name="message_ids" widget="mail_thread"/>
- </div>
+ </page>
+ </notebook>
+ </sheet>
+ <div class="oe_chatter">
+ <field name="message_follower_ids" widget="mail_followers"/>
+ <field name="message_ids" widget="mail_thread"/>
+ </div>
</form>
</field>
</record>
+ <!-- Wizard for Reject Reason -->
+ <record id="view_reject_reason_wizard_form" model="ir.ui.view">
+ <field name="name">reject.reason.commision.form</field>
+ <field name="model">reject.reason.commision</field>
+ <field name="arch" type="xml">
+ <form string="Reject Reason">
+ <group>
+ <field name="reason_reject" widget="text"/>
+ </group>
+ <footer>
+ <button string="Confirm" type="object" name="confirm_reject" class="btn-primary"/>
+ <button string="Cancel" class="btn-secondary" special="cancel"/>
+ </footer>
+ </form>
+ </field>
+ </record>
+
+ <record id="action_reject_reason_wizard" model="ir.actions.act_window">
+ <field name="name">Reject Reason</field>
+ <field name="res_model">reject.reason.commision</field>
+ <field name="view_mode">form</field>
+ <field name="target">new</field>
+ </record>
+
<record id="view_customer_commision_filter" model="ir.ui.view">
<field name="name">customer.commision.list.select</field>
<field name="model">customer.commision</field>
@@ -109,7 +161,12 @@
<field name="arch" type="xml">
<search string="Search Customer Commision">
<field name="partner_ids"/>
- </search>
+ <group expand="0" string="Group By">
+ <filter string="Partner" name="group_partner"
+ domain="[]"
+ context="{'group_by':'partner_ids'}"/>
+ </group>
+ </search>
</field>
</record>
@@ -122,17 +179,17 @@
</record>
<menuitem id="menu_customer_commision_acct"
- name="Customer Commision"
- action="customer_commision_action"
- parent="account.menu_finance_entries"
- sequence="113"
+ name="Customer Commision"
+ action="customer_commision_action"
+ parent="account.menu_finance_entries"
+ sequence="113"
/>
<menuitem id="menu_customer_commision_sales"
- name="Customer Commision"
- action="customer_commision_action"
- parent="sale.product_menu_catalog"
- sequence="101"
+ name="Customer Commision"
+ action="customer_commision_action"
+ parent="sale.product_menu_catalog"
+ sequence="101"
/>
<record id="customer_rebate_tree" model="ir.ui.view">
@@ -166,34 +223,34 @@
<field name="arch" type="xml">
<form>
<sheet string="Customer Rebate">
- <div class="oe_button_box" name="button_box"/>
+ <div class="oe_button_box" name="button_box"/>
+ <group>
<group>
- <group>
- <field name="date_from"/>
- <field name="partner_id"/>
- <field name="target_1st"/>
- <field name="target_2nd"/>
- <field name="dpp_q1"/>
- <field name="dpp_q2"/>
- <field name="dpp_q3"/>
- <field name="dpp_q4"/>
- </group>
- <group>
- <field name="date_to"/>
- <field name="description"/>
- <field name="achieve_1"/>
- <field name="achieve_2"/>
- <field name="status_q1"/>
- <field name="status_q2"/>
- <field name="status_q3"/>
- <field name="status_q4"/>
- </group>
+ <field name="date_from"/>
+ <field name="partner_id"/>
+ <field name="target_1st"/>
+ <field name="target_2nd"/>
+ <field name="dpp_q1"/>
+ <field name="dpp_q2"/>
+ <field name="dpp_q3"/>
+ <field name="dpp_q4"/>
+ </group>
+ <group>
+ <field name="date_to"/>
+ <field name="description"/>
+ <field name="achieve_1"/>
+ <field name="achieve_2"/>
+ <field name="status_q1"/>
+ <field name="status_q2"/>
+ <field name="status_q3"/>
+ <field name="status_q4"/>
</group>
- </sheet>
- <div class="oe_chatter">
- <field name="message_follower_ids" widget="mail_followers"/>
- <field name="message_ids" widget="mail_thread"/>
- </div>
+ </group>
+ </sheet>
+ <div class="oe_chatter">
+ <field name="message_follower_ids" widget="mail_followers"/>
+ <field name="message_ids" widget="mail_thread"/>
+ </div>
</form>
</field>
</record>
@@ -206,16 +263,16 @@
</record>
<menuitem id="menu_customer_rebate_acct"
- name="Customer Rebate"
- action="customer_rebate_action"
- parent="account.menu_finance_entries"
- sequence="114"
+ name="Customer Rebate"
+ action="customer_rebate_action"
+ parent="account.menu_finance_entries"
+ sequence="114"
/>
<menuitem id="menu_customer_rebate_sales"
- name="Customer Rebate"
- action="customer_rebate_action"
- parent="sale.product_menu_catalog"
- sequence="102"
+ name="Customer Rebate"
+ action="customer_rebate_action"
+ parent="sale.product_menu_catalog"
+ sequence="102"
/>
</odoo> \ No newline at end of file
diff --git a/indoteknik_custom/views/ir_sequence.xml b/indoteknik_custom/views/ir_sequence.xml
index dfb56100..97bf40bb 100644
--- a/indoteknik_custom/views/ir_sequence.xml
+++ b/indoteknik_custom/views/ir_sequence.xml
@@ -21,6 +21,16 @@
<field name="number_increment">1</field>
</record>
+ <record id="sequence_invoice_date" model="ir.sequence">
+ <field name="name">Approval Invoice Date</field>
+ <field name="code">approval.invoice.date</field>
+ <field name="active">TRUE</field>
+ <field name="prefix">AID/%(year)s/</field>
+ <field name="padding">5</field>
+ <field name="number_next">1</field>
+ <field name="number_increment">1</field>
+ </record>
+
<record id="sequence_vendor_approval" model="ir.sequence">
<field name="name">Vendor Approval</field>
<field name="code">vendor.approval</field>
@@ -55,7 +65,7 @@
<field name="name">Shipment Group</field>
<field name="code">shipment.group</field>
<field name="active">TRUE</field>
- <field name="prefix">SG/%(year)s/</field>
+ <field name="prefix">SGR/%(year)s/</field>
<field name="padding">5</field>
<field name="number_next">1</field>
<field name="number_increment">1</field>
diff --git a/indoteknik_custom/views/mrp_production.xml b/indoteknik_custom/views/mrp_production.xml
index f81d65e8..3de52a08 100644
--- a/indoteknik_custom/views/mrp_production.xml
+++ b/indoteknik_custom/views/mrp_production.xml
@@ -5,9 +5,38 @@
<field name="model">mrp.production</field>
<field name="inherit_id" ref="mrp.mrp_production_form_view" />
<field name="arch" type="xml">
+ <button name="button_mark_done" position="after">
+ <button name="create_po_from_manufacturing" type="object" string="Create PO" class="oe_highlight" attrs="{'invisible': ['|', ('state', '!=', 'confirmed'), ('is_po', '=', True)]}"/>
+ </button>
<field name="bom_id" position="after">
<field name="desc"/>
+ <field name="sale_order"/>
+ <field name="is_po"/>
</field>
+ <xpath expr="//form/sheet/notebook/page/field[@name='move_raw_ids']/tree/field[@name='product_uom_qty']" position="before">
+ <field name="vendor_id"/>
+ </xpath>
+ <xpath expr="//form/sheet/notebook/page[@name='miscellaneous']" position="after">
+ <page string="Purchase Match" name="purchase_order_lines_indent">
+ <field name="production_purchase_match"/>
+ </page>
+ <page string="Check Product" name="check_bom_product">
+ <field name="check_bom_product_lines"/>
+ </page>
+ </xpath>
+ </field>
+ </record>
+
+ <record id="check_bom_product_tree" model="ir.ui.view">
+ <field name="name">check.bom.product.tree</field>
+ <field name="model">check.bom.product</field>
+ <field name="arch" type="xml">
+ <tree editable="bottom" decoration-warning="status == 'Pending'" decoration-success="status == 'Done'">
+ <field name="code_product"/>
+ <field name="product_id"/>
+ <field name="quantity"/>
+ <field name="status" readonly="1"/>
+ </tree>
</field>
</record>
@@ -18,7 +47,24 @@
<field name="arch" type="xml">
<field name="product_id" position="after">
<field name="desc"/>
+ <field name="sale_order"/>
+ </field>
+ <field name="state" position="after">
+ <field name="state_reserve" optional="hide"/>
+ <field name="date_reserved" optional="hide"/>
</field>
</field>
</record>
+
+ <record id="production_purchase_match_tree" model="ir.ui.view">
+ <field name="name">production.purchase.match.tree</field>
+ <field name="model">production.purchase.match</field>
+ <field name="arch" type="xml">
+ <tree>
+ <field name="order_id" readonly="1"/>
+ <field name="vendor" readonly="1"/>
+ <field name="total" readonly="1"/>
+ </tree>
+ </field>
+ </record>
</odoo>
diff --git a/indoteknik_custom/views/product_pricelist.xml b/indoteknik_custom/views/product_pricelist.xml
index 6eff0153..3c2b8b8d 100644
--- a/indoteknik_custom/views/product_pricelist.xml
+++ b/indoteknik_custom/views/product_pricelist.xml
@@ -20,6 +20,7 @@
<field name="banner_top" widget="image" />
<field name="start_date" attrs="{'required': [('is_flash_sale', '=', True)]}" />
<field name="end_date" attrs="{'required': [('is_flash_sale', '=', True)]}" />
+ <field name="number" required="1" />
</group>
</group>
</page>
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/product_template.xml b/indoteknik_custom/views/product_template.xml
index af21984a..8f9d1190 100755
--- a/indoteknik_custom/views/product_template.xml
+++ b/indoteknik_custom/views/product_template.xml
@@ -53,6 +53,21 @@
<field name="supplier_taxes_id" position="after">
<field name="supplier_url" widget="url"/>
</field>
+ <notebook position="inside">
+ <page string="Image Carousel">
+ <field name="image_carousel_lines"/>
+ </page>
+ </notebook>
+ </field>
+ </record>
+
+ <record id="image_carousel_tree" model="ir.ui.view">
+ <field name="name">image.carousel.tree</field>
+ <field name="model">image.carousel</field>
+ <field name="arch" type="xml">
+ <tree editable="bottom">
+ <field name="image" widget="image" width="80"/>
+ </tree>
</field>
</record>
@@ -62,6 +77,8 @@
<field name="inherit_id" ref="product.product_normal_form_view"/>
<field name="arch" type="xml">
<field name="last_update_solr" position="after">
+ <field name="barcode_box" />
+ <field name="qty_pcs_box" />
<field name="clean_website_description" />
<field name="qr_code_variant" widget="image" readonly="True"/>
</field>
diff --git a/indoteknik_custom/views/project_views.xml b/indoteknik_custom/views/project_views.xml
new file mode 100644
index 00000000..3023fa18
--- /dev/null
+++ b/indoteknik_custom/views/project_views.xml
@@ -0,0 +1,13 @@
+<odoo>
+ <record id="view_task_kanban_inherit" model="ir.ui.view">
+ <field name="name">project.task.kanban.inherit</field>
+ <field name="model">project.task</field>
+ <field name="inherit_id" ref="project.view_task_kanban"/>
+ <field name="arch" type="xml">
+ <!-- Target field user_id di bagian kanban_bottom_right -->
+ <xpath expr="//div[@class='oe_kanban_bottom_right']/field[@name='user_id']" position="attributes">
+ <attribute name="widget" remove="many2one_avatar_user" />
+ </xpath>
+ </field>
+ </record>
+</odoo> \ No newline at end of file
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/purchase_order.xml b/indoteknik_custom/views/purchase_order.xml
index 36c0db13..b58139c6 100755
--- a/indoteknik_custom/views/purchase_order.xml
+++ b/indoteknik_custom/views/purchase_order.xml
@@ -65,6 +65,7 @@
<field name="payment_term_id"/>
<field name="total_cost_service" attrs="{'required': [('partner_id', 'in', [9688, 29712])]}"/>
<field name="total_delivery_amt" attrs="{'required': [('partner_id', 'in', [9688, 29712])]}"/>
+ <field name="product_bom_id"/>
</field>
<field name="amount_total" position="after">
<field name="total_margin"/>
@@ -139,7 +140,7 @@
</field>
<field name="order_line" position="attributes">
- <attribute name="attrs">{'readonly': ['|', ('state', 'in', ['done', 'cancel']), ('has_active_invoice', '=', True)]}</attribute>
+ <attribute name="attrs">{'readonly': ['|', ('state', 'in', ['purchase', 'done', 'cancel']), ('has_active_invoice', '=', True)]}</attribute>
</field>
<xpath expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='price_unit']" position="attributes">
@@ -307,6 +308,7 @@
<field name="margin_item" optional="hide"/>
<field name="delivery_amt" optional="hide"/>
<field name="margin_deduct" optional="hide"/>
+ <field name="hold_outgoing_so" optional="hide"/>
<field name="margin_so"/>
</tree>
</field>
diff --git a/indoteknik_custom/views/purchasing_job.xml b/indoteknik_custom/views/purchasing_job.xml
index 16f1bedd..bb1c7643 100644
--- a/indoteknik_custom/views/purchasing_job.xml
+++ b/indoteknik_custom/views/purchasing_job.xml
@@ -17,6 +17,7 @@
<field name="status_apo" invisible="1"/>
<field name="action"/>
<field name="note"/>
+ <field name="date_po"/>
</tree>
</field>
</record>
diff --git a/indoteknik_custom/views/res_partner.xml b/indoteknik_custom/views/res_partner.xml
index af5e0db3..5160523f 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"/>
@@ -187,6 +190,66 @@
</group>
</page>
</notebook>
+ <notebook>
+ <page string="Merchant">
+ <group>
+ <group string="Informasi Perusahaan">
+ <field name="name_merchant" />
+ <field name="pejabat_name" />
+ <field name="pic_merchant" />
+ <field name="pic_position" />
+ <field name="address_merchant" />
+ <field name="state_merchant" />
+ <field name="city_merchant" />
+ <field name="district_merchant" />
+ <field name="subDistrict_merchant" />
+ <field name="zip_merchant" />
+ <field name="bank_name_merchant" />
+ <field name="rekening_name_merchant" />
+ <field name="account_number_merchant" />
+ <field name="email_company_merchant" widget="email"/>
+ <field name="email_sales_merchant" widget="email"/>
+ <field name="email_finance_merchant" widget="email"/>
+ <field name="phone_merchant" widget="phone"/>
+ <field name="mobile_merchant" widget="phone"/>
+ <field name="bisnis_type" />
+ <field name="website_merchant" />
+ <field name="category_perusahaan" />
+ </group>
+ <group string="Syarat Perdagangan">
+ <field name="is_kembali_barang" />
+ <field name="tenggat_waktu" />
+ <field name="sertifikat_produk" />
+ <field name="tempo_garansi" />
+ <field name="explain_garansi" />
+ <field name="is_order_quantity" />
+ </group>
+ <group string="Informasi Vendor">
+ <field name="harga_tayang" />
+ <field name="category_produk_ids_merchant" widget="many2many_tags" />
+ <field name="merk_dagang" />
+ <field name="is_pengajuan_tempo" />
+ <field name="tempo_duration_merchant" />
+ <field name="kredit_limit" />
+ <field name="waktu_pengiriman" />
+ <field name="terhitung_sejak" />
+ </group>
+ <group string="Dokumen">
+ <field name="file_npwp" />
+ <field name="file_sppkp" />
+ <field name="file_dokumenKtpDirut" />
+ <field name="file_kartuNama" />
+ <field name="file_suratPernyataan" />
+ <field name="file_fotoKantor" />
+ <field name="file_dataProduk" />
+ <field name="file_pricelist" />
+ </group>
+ <group>
+<!-- <field name="description" />-->
+ </group>
+ </group>
+ </page>
+ </notebook>
</field>
</record>
</data>
diff --git a/indoteknik_custom/views/sale_order.xml b/indoteknik_custom/views/sale_order.xml
index 163330c5..5af3f4a2 100755
--- a/indoteknik_custom/views/sale_order.xml
+++ b/indoteknik_custom/views/sale_order.xml
@@ -1,174 +1,226 @@
-<?xml version="1.0" encoding="UTF-8" ?>
+<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data>
<record id="sale_order_form_view_inherit" model="ir.ui.view">
<field name="name">Sale Order</field>
<field name="model">sale.order</field>
- <field name="inherit_id" ref="sale.view_order_form"/>
+ <field name="inherit_id" ref="sale.view_order_form" />
<field name="arch" type="xml">
<button id="action_confirm" position="after">
<button name="calculate_line_no"
- string="Create No"
- type="object"
+ string="Create No"
+ type="object"
/>
<button name="sale_order_approve"
- string="Ask Approval"
- type="object"
- attrs="{'invisible': [('approval_status', '=', ['approved'])]}"
+ string="Ask Approval"
+ type="object"
+ attrs="{'invisible': [('approval_status', '=', ['approved'])]}"
+ />
+ <button name="hold_unhold_qty_outgoing_so"
+ string="Hold/Unhold Outgoing"
+ type="object"
+ attrs="{'invisible': [('state', 'in', ['cancel'])]}"
+ />
+ <button name="ask_retur_cancel_purchasing"
+ string="Ask Cancel Purchasing"
+ type="object"
+ attrs="{'invisible': [('state', 'in', ['cancel'])]}"
/>
<button name="action_web_approve"
- string="Web Approve"
- type="object"
- attrs="{'invisible': ['|', '|', ('create_uid', '!=', 25), ('web_approval', '!=', False), ('state', '!=', 'draft')]}"
+ string="Web Approve"
+ type="object"
+ attrs="{'invisible': ['|', '|', ('create_uid', '!=', 25), ('web_approval', '!=', False), ('state', '!=', 'draft')]}"
/>
- <button name="indoteknik_custom.action_view_uangmuka_penjualan" string="UangMuka"
- type="action" attrs="{'invisible': [('approval_status', '!=', 'approved')]}"/>
+ <button name="indoteknik_custom.action_view_uangmuka_penjualan"
+ string="UangMuka"
+ type="action" attrs="{'invisible': [('approval_status', '!=', 'approved')]}" />
</button>
<field name="payment_term_id" position="after">
- <field name="create_uid" invisible="1"/>
- <field name="create_date" invisible="1"/>
- <field name="shipping_cost_covered" attrs="{'required': ['|', ('create_date', '&gt;', '2023-06-15'), ('create_date', '=', False)]}"/>
- <field name="shipping_paid_by" attrs="{'required': ['|', ('create_date', '&gt;', '2023-06-15'), ('create_date', '=', False)]}"/>
- <field name="delivery_amt"/>
- <field name="fee_third_party"/>
- <field name="total_percent_margin"/>
- <field name="type_promotion"/>
- <label for="voucher_id"/>
+ <field name="create_uid" invisible="1" />
+ <field name="create_date" invisible="1" />
+ <field name="shipping_cost_covered"
+ attrs="{'required': ['|', ('create_date', '&gt;', '2023-06-15'), ('create_date', '=', False)]}" />
+ <field name="shipping_paid_by"
+ attrs="{'required': ['|', ('create_date', '&gt;', '2023-06-15'), ('create_date', '=', False)]}" />
+ <field name="delivery_amt" />
+ <field name="ongkir_ke_xpdc" />
+ <field name="metode_kirim_ke_xpdc" />
+ <field name="fee_third_party" />
+ <field name="biaya_lain_lain" />
+ <field name="total_percent_margin" />
+ <field name="total_margin_excl_third_party" readonly="1" />
+ <field name="type_promotion" />
+ <label for="voucher_id" />
<div class="o_row">
- <field name="voucher_id" id="voucher_id" attrs="{'readonly': ['|', ('state', 'not in', ['draft', 'sent']), ('applied_voucher_id', '!=', False)]}"/>
+ <field name="voucher_id" id="voucher_id"
+ attrs="{'readonly': ['|', ('state', 'not in', ['draft', 'sent']), ('applied_voucher_id', '!=', False)]}" />
<field name="applied_voucher_id" invisible="1" />
- <button name="action_apply_voucher" type="object" string="Apply" confirm="Anda yakin untuk menggunakan voucher?" help="Apply the selected voucher" class="btn-link mb-1 px-0" icon="fa-plus"
+ <button name="action_apply_voucher" type="object" string="Apply"
+ confirm="Anda yakin untuk menggunakan voucher?"
+ help="Apply the selected voucher" class="btn-link mb-1 px-0"
+ icon="fa-plus"
attrs="{'invisible': ['|', '|', ('voucher_id', '=', False), ('state', 'not in', ['draft', 'sent']), ('applied_voucher_id', '!=', False)]}"
/>
- <button name="cancel_voucher" type="object" string="Cancel" confirm="Anda yakin untuk membatalkan penggunaan voucher?" help="Cancel applied voucher" class="btn-link mb-1 px-0" icon="fa-times"
+ <button name="cancel_voucher" type="object" string="Cancel"
+ confirm="Anda yakin untuk membatalkan penggunaan voucher?"
+ help="Cancel applied voucher" class="btn-link mb-1 px-0" icon="fa-times"
attrs="{'invisible': ['|', ('applied_voucher_id', '=', False), ('state', 'not in', ['draft','sent'])]}"
/>
</div>
- <label for="voucher_shipping_id"/>
+ <label for="voucher_shipping_id" />
<div class="o_row">
- <field name="voucher_shipping_id" id="voucher_shipping_id" attrs="{'readonly': ['|', ('state', 'not in', ['draft', 'sent']), ('applied_voucher_shipping_id', '!=', False)]}"/>
+ <field name="voucher_shipping_id" id="voucher_shipping_id"
+ attrs="{'readonly': ['|', ('state', 'not in', ['draft', 'sent']), ('applied_voucher_shipping_id', '!=', False)]}" />
<field name="applied_voucher_shipping_id" invisible="1" />
- <button name="action_apply_voucher_shipping" type="object" string="Apply" confirm="Anda yakin untuk menggunakan voucher?" help="Apply the selected voucher" class="btn-link mb-1 px-0" icon="fa-plus"
+ <button name="action_apply_voucher_shipping" type="object" string="Apply"
+ confirm="Anda yakin untuk menggunakan voucher?"
+ help="Apply the selected voucher" class="btn-link mb-1 px-0"
+ icon="fa-plus"
attrs="{'invisible': ['|', '|', ('voucher_id', '=', False), ('state', 'not in', ['draft', 'sent']), ('applied_voucher_shipping_id', '!=', False)]}"
/>
- <button name="cancel_voucher_shipping" type="object" string="Cancel" confirm="Anda yakin untuk membatalkan penggunaan voucher?" help="Cancel applied voucher" class="btn-link mb-1 px-0" icon="fa-times"
+ <button name="cancel_voucher_shipping" type="object" string="Cancel"
+ confirm="Anda yakin untuk membatalkan penggunaan voucher?"
+ help="Cancel applied voucher" class="btn-link mb-1 px-0" icon="fa-times"
attrs="{'invisible': ['|', ('applied_voucher_shipping_id', '=', False), ('state', 'not in', ['draft','sent'])]}"
/>
</div>
<button name="calculate_selling_price"
- string="Calculate Selling Price"
- type="object"
+ string="Calculate Selling Price"
+ type="object"
/>
</field>
<field name="source_id" position="attributes">
<attribute name="invisible">1</attribute>
</field>
<field name="user_id" position="after">
- <field name="helper_by_id" readonly="1"/>
- <field name="compute_fullfillment" invisible="1"/>
+ <field name="hold_outgoing" readonly="1" />
+ <field name="helper_by_id" readonly="1" />
+ <field name="compute_fullfillment" invisible="1" />
</field>
<field name="tag_ids" position="after">
- <field name="eta_date" readonly="1"/>
- <field name="flash_sale"/>
- <field name="margin_after_delivery_purchase"/>
- <field name="percent_margin_after_delivery_purchase"/>
- <field name="total_weight"/>
- <field name="pareto_status"/>
+ <field name="eta_date_start" />
+ <t t-esc="' to '" />
+ <field name="eta_date" readonly="1" />
+ <field name="expected_ready_to_ship" />
+ <field name="ready_to_ship_status_detail"/>
+ <field name="flash_sale" />
+ <field name="margin_after_delivery_purchase" />
+ <field name="percent_margin_after_delivery_purchase" />
+ <field name="total_weight" />
+ <field name="pareto_status" />
</field>
<field name="analytic_account_id" position="after">
- <field name="customer_type" required="1"/>
- <field name="npwp" placeholder='99.999.999.9-999.999' required="1"/>
- <field name="sppkp" attrs="{'required': [('customer_type', '=', 'pkp')]}"/>
- <field name="email" required="1"/>
- <field name="unreserve_id"/>
- <field name="due_id" readonly="1"/>
- <field name="vendor_approval_id" readonly="1" widget="many2many_tags"/>
- <field name="source_id" domain="[('id', 'in', [32, 59, 60, 61])]" required="1"/>
+ <field name="customer_type" readonly="1" />
+ <field name="npwp" placeholder='99.999.999.9-999.999' readonly="1" />
+ <field name="sppkp" attrs="{'required': [('customer_type', '=', 'pkp')]}" readonly="1" />
+ <field name="email" required="1" />
+ <field name="unreserve_id" />
+ <field name="due_id" readonly="1" />
+ <field name="vendor_approval_id" readonly="1" widget="many2many_tags" />
+ <field name="source_id" domain="[('id', 'in', [32, 59, 60, 61])]" required="1" />
<button name="override_allow_create_invoice"
- string="Override Create Invoice"
- type="object"
+ string="Override Create Invoice"
+ type="object"
/>
- <button string="Estimate Shipping" type="object" name="action_estimate_shipping"/>
+ <button string="Estimate Shipping" type="object" name="action_estimate_shipping" />
</field>
<field name="partner_shipping_id" position="after">
- <field name="real_shipping_id"/>
- <field name="real_invoice_id"/>
+ <field name="real_shipping_id" />
+ <field name="real_invoice_id" />
<field name="approval_status" />
- <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="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"/>
- <field name="notification" readonly="1"/>
+ <field name="date_doc_kirim" readonly="1" />
+ <field name="notification" readonly="1" />
</field>
- <xpath expr="//form/sheet/notebook/page/field[@name='order_line']" position="attributes">
+ <xpath expr="//form/sheet/notebook/page/field[@name='order_line']"
+ position="attributes">
<attribute name="attrs">
{'readonly': [('state', 'in', ('done','cancel'))]}
</attribute>
</xpath>
- <xpath expr="//form/sheet/notebook/page/field[@name='order_line']/tree" position="inside">
- <field name="desc_updatable" invisible="1"/>
+ <xpath expr="//form/sheet/notebook/page/field[@name='order_line']/tree"
+ position="inside">
+ <field name="desc_updatable" invisible="1" />
</xpath>
- <xpath expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='name']" position="attributes">
+ <xpath
+ expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='name']"
+ position="attributes">
<attribute name="modifiers">
{'readonly': [('desc_updatable', '=', False)]}
</attribute>
</xpath>
- <xpath expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='price_unit']" position="attributes">
+ <xpath
+ expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='price_unit']"
+ position="attributes">
<attribute name="attrs">
{
- 'readonly': [
- '|',
- ('qty_invoiced', '>', 0),
- ('parent.approval_status', '!=', False)
- ]
+ 'readonly': [
+ '|',
+ ('qty_invoiced', '>', 0),
+ ('parent.approval_status', '!=', False)
+ ]
}
</attribute>
</xpath>
<div name="invoice_lines" position="before">
- <div name="vendor_id" groups="base.group_no_one" attrs="{'invisible': [('display_type', '!=', False)]}">
- <label for="vendor_id"/>
+ <div name="vendor_id" groups="base.group_no_one"
+ attrs="{'invisible': [('display_type', '!=', False)]}">
+ <label for="vendor_id" />
<div name="vendor_id">
- <field name="vendor_id"
- attrs="{'readonly': [('parent.approval_status', '=', 'approved')]}"
- domain="[('parent_id', '=', False)]"
- options="{'no_create': True}" class="oe_inline" />
+ <field name="vendor_id"
+ attrs="{'readonly': [('parent.approval_status', '=', 'approved')]}"
+ domain="[('parent_id', '=', False)]"
+ options="{'no_create': True}" class="oe_inline" />
</div>
</div>
</div>
-
+
<div name="invoice_lines" position="before">
- <div name="purchase_price" groups="base.group_no_one" attrs="{'invisible': [('display_type', '!=', False)]}">
- <label for="purchase_price"/>
- <field name="purchase_price"/>
+ <div name="purchase_price" groups="base.group_no_one"
+ attrs="{'invisible': [('display_type', '!=', False)]}">
+ <label for="purchase_price" />
+ <field name="purchase_price" />
</div>
</div>
<div name="invoice_lines" position="before">
- <div name="purchase_tax_id" groups="base.group_no_one" attrs="{'invisible': [('display_type', '!=', False)]}">
- <label for="purchase_tax_id"/>
+ <div name="purchase_tax_id" groups="base.group_no_one"
+ attrs="{'invisible': [('display_type', '!=', False)]}">
+ <label for="purchase_tax_id" />
<div name="purchase_tax_id">
- <field name="purchase_tax_id"/>
+ <field name="purchase_tax_id" />
</div>
</div>
</div>
<div name="invoice_lines" position="before">
- <div name="item_percent_margin" groups="base.group_no_one" attrs="{'invisible': [('display_type', '!=', False)]}">
- <label for="item_percent_margin"/>
- <field name="item_percent_margin"/>
+ <div name="item_percent_margin" groups="base.group_no_one"
+ attrs="{'invisible': [('display_type', '!=', False)]}">
+ <label for="item_percent_margin" />
+ <field name="item_percent_margin" />
</div>
</div>
<div name="invoice_lines" position="before">
- <div name="price_subtotal" groups="base.group_no_one" attrs="{'invisible': [('display_type', '!=', False)]}">
- <label for="price_subtotal"/>
- <field name="price_subtotal"/>
+ <div name="price_subtotal" groups="base.group_no_one"
+ attrs="{'invisible': [('display_type', '!=', False)]}">
+ <label for="price_subtotal" />
+ <field name="price_subtotal" />
</div>
</div>
- <xpath expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='price_total']" position="after">
- <field name="qty_free_bu" optional="hide"/>
- <field name="vendor_id" attrs="{'readonly': [('parent.approval_status', '=', 'approved')], 'invisible': [('display_type', '!=', False)]}" domain="[('parent_id', '=', False)]" options="{'no_create':True}"/>
- <field name="vendor_md_id" optional="hide"/>
- <field name="purchase_price" attrs="
+ <xpath
+ expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='price_total']"
+ position="after">
+ <field name="qty_free_bu" optional="hide" />
+ <field name="vendor_id"
+ attrs="{'readonly': [('parent.approval_status', '=', 'approved')], 'invisible': [('display_type', '!=', False)]}"
+ domain="[('parent_id', '=', False)]" options="{'no_create':True}" />
+ <field name="vendor_md_id" optional="hide" />
+ <field name="purchase_price"
+ attrs="
{
'readonly': [
'|',
@@ -176,56 +228,79 @@
('parent.approval_status', '!=', False)
]
}
- "/>
- <field name="purchase_price_md" optional="hide"/>
- <field name="purchase_tax_id" attrs="{'readonly': [('parent.approval_status', '!=', False)]}" domain="[('type_tax_use','=','purchase')]" options="{'no_create':True}"/>
- <field name="item_percent_margin"/>
- <field name="item_margin" optional="hide"/>
- <field name="margin_md" optional="hide"/>
- <field name="note" optional="hide"/>
- <field name="note_procurement" optional="hide"/>
- <field name="vendor_subtotal" optional="hide"/>
- <field name="weight" optional="hide"/>
- <field name="amount_voucher_disc" string="Voucher" readonly="1" optional="hide"/>
- <field name="order_promotion_id" string="Promotion" readonly="1" optional="hide"/>
+ " />
+ <field name="purchase_price_md" optional="hide" />
+ <field name="purchase_tax_id"
+ attrs="{'readonly': [('parent.approval_status', '!=', False)]}"
+ domain="[('type_tax_use','=','purchase')]" options="{'no_create':True}" />
+ <field name="item_percent_margin" />
+ <field name="item_margin" optional="hide" />
+ <field name="margin_md" optional="hide" />
+ <field name="note" optional="hide" />
+ <field name="note_procurement" optional="hide" />
+ <field name="vendor_subtotal" optional="hide" />
+ <field name="weight" optional="hide" />
+ <field name="amount_voucher_disc" string="Voucher" readonly="1" optional="hide" />
+ <field name="order_promotion_id" string="Promotion" readonly="1" optional="hide" />
</xpath>
- <xpath expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='product_id']" position="before">
- <field name="line_no" readonly="1" optional="hide"/>
+ <xpath
+ expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='product_id']"
+ position="before">
+ <field name="line_no" readonly="1" optional="hide" />
</xpath>
- <xpath expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='qty_delivered']" position="before">
- <field name="qty_reserved" invisible="1"/>
- <field name="reserved_from" readonly="1" optional="hide"/>
+ <xpath
+ expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='qty_delivered']"
+ position="before">
+ <field name="qty_reserved" invisible="1" />
+ <field name="reserved_from" readonly="1" optional="hide" />
</xpath>
- <xpath expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='product_id']" position="attributes">
+ <xpath
+ expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='product_id']"
+ position="attributes">
<attribute name="options">{'no_create': True}</attribute>
</xpath>
- <!-- <xpath expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='tax_id']" position="attributes">
+ <!-- <xpath
+ expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='tax_id']"
+ position="attributes">
<attribute name="required">1</attribute>
</xpath> -->
<field name="amount_total" position="after">
- <field name="grand_total"/>
+ <field name="grand_total" />
<label for="amount_voucher_disc" string="Voucher" />
<div>
- <field class="mb-0" name="amount_voucher_disc" string="Voucher" readonly="1"/>
- <div class="text-right mb-2"><small>*Hanya informasi</small></div>
+ <field class="mb-0" name="amount_voucher_disc" string="Voucher" readonly="1" />
+ <div class="text-right mb-2">
+ <small>*Hanya informasi</small>
+ </div>
</div>
<label for="amount_voucher_shipping_disc" string="Voucher Shipping" />
<div>
- <field class="mb-0" name="amount_voucher_shipping_disc" string="Voucher Shipping" readonly="1"/>
- <div class="text-right mb-2"><small>*Hanya informasi</small></div>
+ <field class="mb-0" name="amount_voucher_shipping_disc"
+ string="Voucher Shipping" readonly="1" />
+ <div class="text-right mb-2">
+ <small>*Hanya informasi</small>
+ </div>
</div>
- <field name="total_margin"/>
- <field name="total_percent_margin"/>
+ <field name="total_margin" />
+ <field name="total_percent_margin" />
+ <field name="total_before_margin" />
</field>
<field name="effective_date" position="after">
- <field name="carrier_id"/>
- <field name="estimated_arrival_days"/>
- <field name="picking_iu_id"/>
- <field name="note_ekspedisi"/>
+ <field name="carrier_id" />
+ <field name="estimated_arrival_days" />
+ <field name="picking_iu_id" />
+ <field name="note_ekspedisi" />
</field>
<field name="carrier_id" position="attributes">
<attribute name="attrs">
- {'readonly': [('approval_status', '=', 'approved'), ('state', 'not in', ['cancel','draft'])]}
+ {'readonly': [('approval_status', '=', 'approved'), ('state', 'not in',
+ ['cancel','draft'])]}
+ </attribute>
+ </field>
+ <field name="payment_term_id" position="attributes">
+ <attribute name="attrs">
+ {'readonly': [('approval_status', '=', 'approved'), ('state', 'not in',
+ ['cancel','draft'])]}
</attribute>
</field>
@@ -233,22 +308,22 @@
<page string="Website" name="customer_purchase_order">
<group>
<group>
- <field name="partner_purchase_order_name" readonly="True"/>
- <field name="partner_purchase_order_description" readonly="True"/>
- <field name="partner_purchase_order_file" readonly="True"/>
- <field name="note_website" readonly="True"/>
- <field name="web_approval" readonly="True"/>
+ <field name="partner_purchase_order_name" readonly="True" />
+ <field name="partner_purchase_order_description" readonly="True" />
+ <field name="partner_purchase_order_file" readonly="True" />
+ <field name="note_website" readonly="True" />
+ <field name="web_approval" readonly="True" />
</group>
<group>
<button name="generate_payment_link_midtrans_sales_order"
- string="Create Payment Link"
- type="object"
+ string="Create Payment Link"
+ type="object"
/>
- <field name="payment_link_midtrans" readonly="True" widget="url"/>
- <field name="gross_amount" readonly="True"/>
- <field name="payment_type" readonly="True"/>
- <field name="payment_status" readonly="True"/>
- <field name="payment_qr_code" widget="image" readonly="True"/>
+ <field name="payment_link_midtrans" readonly="True" widget="url" />
+ <field name="gross_amount" readonly="True" />
+ <field name="payment_type" readonly="True" />
+ <field name="payment_status" readonly="True" />
+ <field name="payment_qr_code" widget="image" readonly="True" />
</group>
</group>
</page>
@@ -269,86 +344,92 @@
</field>
</page>
<page string="Matches PO" name="page_matches_po" invisible="1">
- <field name="order_sales_match_line" readonly="1"/>
+ <field name="order_sales_match_line" readonly="1" />
</page>
<!-- <page string="Fullfillment" name="page_sale_order_fullfillment">
<field name="fullfillment_line" readonly="1"/>
</page> -->
<page string="Fulfillment v2" name="page_sale_order_fullfillment2">
- <field name="fulfillment_line_v2" readonly="1"/>
+ <field name="fulfillment_line_v2" readonly="1" />
</page>
<page string="Reject Line" name="page_sale_order_reject_line">
- <field name="reject_line" readonly="1"/>
+ <field name="reject_line" readonly="0" />
+ </page>
+ <page string="Koli" name="page_sales_order_koli_line">
+ <field name="koli_lines" readonly="1" />
</page>
</page>
</field>
</record>
- <!-- Wizard for Reject Reason -->
- <record id="view_cancel_reason_order_form" model="ir.ui.view">
- <field name="name">cancel.reason.order.form</field>
- <field name="model">cancel.reason.order</field>
- <field name="arch" type="xml">
- <form string="Cancel Reason">
- <group>
- <field name="reason_cancel" widget="selection"/>
- <field name="attachment_bukti" widget="many2many_binary" required="1"/>
- <field name="nomor_so_pengganti" attrs="{'invisible': [('reason_cancel', '!=', 'ganti_quotation')]}"/>
- </group>
- <footer>
- <button string="Confirm" type="object" name="confirm_reject" class="btn-primary"/>
- <button string="Cancel" class="btn-secondary" special="cancel"/>
- </footer>
- </form>
- </field>
- </record>
+ <!-- Wizard for Reject Reason -->
+ <record id="view_cancel_reason_order_form" model="ir.ui.view">
+ <field name="name">cancel.reason.order.form</field>
+ <field name="model">cancel.reason.order</field>
+ <field name="arch" type="xml">
+ <form string="Cancel Reason">
+ <group>
+ <field name="reason_cancel" widget="selection" />
+ <field name="attachment_bukti" widget="many2many_binary" required="1" />
+ <field name="nomor_so_pengganti"
+ attrs="{'invisible': [('reason_cancel', '!=', 'ganti_quotation')]}" />
+ </group>
+ <footer>
+ <button string="Confirm" type="object" name="confirm_reject"
+ class="btn-primary" />
+ <button string="Cancel" class="btn-secondary" special="cancel" />
+ </footer>
+ </form>
+ </field>
+ </record>
- <record id="action_cancel_reason_order" model="ir.actions.act_window">
- <field name="name">Cancel Reason</field>
- <field name="res_model">cancel.reason.order</field>
- <field name="view_mode">form</field>
- <field name="target">new</field>
- </record>
+ <record id="action_cancel_reason_order" model="ir.actions.act_window">
+ <field name="name">Cancel Reason</field>
+ <field name="res_model">cancel.reason.order</field>
+ <field name="view_mode">form</field>
+ <field name="target">new</field>
+ </record>
</data>
<data>
<record id="sale_order_tree_view_inherit" model="ir.ui.view">
<field name="name">Sale Order</field>
<field name="model">sale.order</field>
- <field name="inherit_id" ref="sale.view_quotation_tree_with_onboarding"/>
+ <field name="inherit_id" ref="sale.view_quotation_tree_with_onboarding" />
<field name="arch" type="xml">
<field name="state" position="after">
<field name="approval_status" />
- <field name="client_order_ref"/>
- <field name="payment_type" optional="hide"/>
- <field name="payment_status" optional="hide"/>
- <field name="pareto_status" optional="hide"/>
- <field name="shipping_method_picking" optional="hide"/>
+ <field name="client_order_ref" />
+ <field name="payment_type" optional="hide" />
+ <field name="payment_status" optional="hide" />
+ <field name="pareto_status" optional="hide" />
+ <field name="shipping_method_picking" optional="hide" />
+ <field name="hold_outgoing" optional="hide" />
</field>
</field>
</record>
<record id="sales_order_tree_view_inherit" model="ir.ui.view">
<field name="name">Sale Order</field>
<field name="model">sale.order</field>
- <field name="inherit_id" ref="sale.view_order_tree"/>
+ <field name="inherit_id" ref="sale.view_order_tree" />
<field name="arch" type="xml">
<field name="state" position="after">
<field name="approval_status" />
- <field name="client_order_ref"/>
- <field name="so_status"/>
- <field name="date_status_done"/>
- <field name="date_kirim_ril"/>
- <field name="date_driver_departure"/>
- <field name="date_driver_arrival"/>
- <field name="payment_type" optional="hide"/>
- <field name="payment_status" optional="hide"/>
- <field name="pareto_status" optional="hide"/>
+ <field name="client_order_ref" />
+ <field name="so_status" />
+ <field name="date_status_done" />
+ <field name="date_kirim_ril" />
+ <field name="date_driver_departure" />
+ <field name="date_driver_arrival" />
+ <field name="payment_type" optional="hide" />
+ <field name="payment_status" optional="hide" />
+ <field name="pareto_status" optional="hide" />
</field>
</field>
</record>
<record id="sale_order_multi_update_ir_actions_server" model="ir.actions.server">
<field name="name">Mark As Cancel</field>
- <field name="model_id" ref="sale.model_sale_order"/>
- <field name="binding_model_id" ref="sale.model_sale_order"/>
+ <field name="model_id" ref="sale.model_sale_order" />
+ <field name="binding_model_id" ref="sale.model_sale_order" />
<field name="binding_view_types">form,list</field>
<field name="state">code</field>
<field name="code">action = records.open_form_multi_update_state()</field>
@@ -356,46 +437,81 @@
<record id="sale_order_update_multi_actions_server" model="ir.actions.server">
<field name="name">Mark As Completed</field>
- <field name="model_id" ref="sale.model_sale_order"/>
- <field name="binding_model_id" ref="sale.model_sale_order"/>
+ <field name="model_id" ref="sale.model_sale_order" />
+ <field name="binding_model_id" ref="sale.model_sale_order" />
<field name="state">code</field>
<field name="code">action = records.open_form_multi_update_status()</field>
</record>
<record id="mail_template_sale_order_web_approve_notification" model="mail.template">
<field name="name">Sale Order: Web Approve Notification</field>
- <field name="model_id" ref="indoteknik_custom.model_sale_order"/>
+ <field name="model_id" ref="indoteknik_custom.model_sale_order" />
<field name="subject">Permintaan Persetujuan Pesanan ${object.name} di Indoteknik.com</field>
<field name="email_from">sales@indoteknik.com</field>
<field name="email_to">${object.partner_id.email | safe}</field>
<field name="email_cc">${object.partner_id.get_approve_partner_ids("email_comma_sep")}</field>
<field name="body_html" type="html">
- <table border="0" cellpadding="0" cellspacing="0" style="padding: 16px 0; background-color: #F1F1F1; font-family:Inter, Helvetica, Verdana, Arial,sans-serif; line-height: 24px; color: #454748; width: 100%; border-collapse:separate;">
- <tr><td align="center">
- <table border="0" cellpadding="0" cellspacing="0" width="590" style="font-size: 13px; padding: 16px; background-color: white; color: #454748; border-collapse:separate;">
- <tbody>
- <tr>
- <td align="center" style="min-width: 590px;">
- <table border="0" cellpadding="0" cellspacing="0" width="590" style="min-width: 590px; background-color: white; padding: 0px 8px 0px 8px; border-collapse:separate;">
- <tr><td style="padding-bottom: 24px;">Dear ${(object.partner_id.get_main_parent()).name},</td></tr>
+ <table border="0" cellpadding="0" cellspacing="0"
+ style="padding: 16px 0; background-color: #F1F1F1; font-family:Inter, Helvetica, Verdana, Arial,sans-serif; line-height: 24px; color: #454748; width: 100%; border-collapse:separate;">
+ <tr>
+ <td align="center">
+ <table border="0" cellpadding="0" cellspacing="0" width="590"
+ style="font-size: 13px; padding: 16px; background-color: white; color: #454748; border-collapse:separate;">
+ <tbody>
+ <tr>
+ <td align="center" style="min-width: 590px;">
+ <table border="0" cellpadding="0" cellspacing="0"
+ width="590"
+ style="min-width: 590px; background-color: white; padding: 0px 8px 0px 8px; border-collapse:separate;">
+ <tr>
+ <td style="padding-bottom: 24px;">
+ Dear
+ ${(object.partner_id.get_main_parent()).name},</td>
+ </tr>
- <tr><td style="padding-bottom: 16px;">Ini adalah konfirmasi pesanan dari ${object.partner_id.name | safe} untuk nomor pesanan ${object.name} yang memerlukan persetujuan agar dapat diproses.</td></tr>
- <tr><td style="padding-bottom: 16px;">
- <a href="https://indoteknik.com/my/quotations/${object.id}" style="color: white; background-color: #C53030; border: none; border-radius: 6px; padding: 4px 8px; width: fit-content; display: block;">
- Lihat Pesanan
- </a>
- </td></tr>
- <tr><td style="padding-bottom: 16px;">Mohon segera melakukan tinjauan terhadap pesanan ini dan memberikan persetujuan. Terima kasih atas perhatian dan kerjasama Anda. Kami berharap dapat segera melanjutkan proses pesanan ini setelah mendapatkan persetujuan Anda.</td></tr>
+ <tr>
+ <td style="padding-bottom: 16px;">Ini adalah
+ konfirmasi pesanan dari
+ ${object.partner_id.name | safe} untuk nomor
+ pesanan ${object.name} yang memerlukan
+ persetujuan agar dapat diproses.</td>
+ </tr>
+ <tr>
+ <td style="padding-bottom: 16px;">
+ <a
+ href="https://indoteknik.com/my/quotations/${object.id}"
+ style="color: white; background-color: #C53030; border: none; border-radius: 6px; padding: 4px 8px; width: fit-content; display: block;">
+ Lihat Pesanan
+ </a>
+ </td>
+ </tr>
+ <tr>
+ <td style="padding-bottom: 16px;">Mohon segera
+ melakukan tinjauan terhadap pesanan ini dan
+ memberikan persetujuan. Terima kasih atas
+ perhatian dan kerjasama Anda. Kami berharap
+ dapat segera melanjutkan proses pesanan ini
+ setelah mendapatkan persetujuan Anda.</td>
+ </tr>
- <tr><td style="padding-bottom: 2px;">Hormat kami,</td></tr>
- <tr><td style="padding-bottom: 2px;">PT. Indoteknik Dotcom Gemilang</td></tr>
- <tr><td style="padding-bottom: 2px;">sales@indoteknik.com</td></tr>
- </table>
- </td>
- </tr>
- </tbody>
- </table>
- </td></tr>
+ <tr>
+ <td style="padding-bottom: 2px;">Hormat kami,</td>
+ </tr>
+ <tr>
+ <td style="padding-bottom: 2px;">PT. Indoteknik
+ Dotcom Gemilang</td>
+ </tr>
+ <tr>
+ <td style="padding-bottom: 2px;">
+ sales@indoteknik.com</td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
</table>
</field>
</record>
@@ -407,45 +523,59 @@
<field name="model">sales.order.purchase.match</field>
<field name="arch" type="xml">
<tree editable="top" create="false" delete="false">
- <field name="purchase_order_id" readonly="1"/>
- <field name="purchase_line_id" readonly="1"/>
- <field name="product_id" readonly="1"/>
- <field name="qty_so" readonly="1"/>
- <field name="qty_po" readonly="1"/>
+ <field name="purchase_order_id" readonly="1" />
+ <field name="purchase_line_id" readonly="1" />
+ <field name="product_id" readonly="1" />
+ <field name="qty_so" readonly="1" />
+ <field name="qty_po" readonly="1" />
</tree>
</field>
</record>
</data>
<data>
-
- </data>
- <record id="sales_order_fulfillment_v2_tree" model="ir.ui.view">
- <field name="name">sales.order.fulfillment.v2.tree</field>
- <field name="model">sales.order.fulfillment.v2</field>
+ <record id="sales_order_koli_tree" model="ir.ui.view">
+ <field name="name">sales.order.koli.tree</field>
+ <field name="model">sales.order.koli</field>
<field name="arch" type="xml">
- <tree editable="top" create="false">
- <field name="product_id" readonly="1"/>
- <field name="so_qty" readonly="1" optional="show"/>
- <field name="reserved_stock_qty" readonly="1" optional="show"/>
- <field name="delivered_qty" readonly="1" optional="hide"/>
- <field name="po_ids" widget="many2many_tags" readonly="1" optional="show"/>
- <field name="po_qty" readonly="1" optional="show"/>
- <field name="received_qty" readonly="1" optional="show"/>
- <field name="purchaser" readonly="1" optional="hide"/>
+ <tree editable="top" create="false" delete="false">
+ <field name="koli_id" readonly="1" />
+ <field name="picking_id" readonly="1" />
+ <field name="state" readonly="1" />
</tree>
</field>
</record>
+ </data>
+
+ <data>
+
+ </data>
+ <record id="sales_order_fulfillment_v2_tree" model="ir.ui.view">
+ <field name="name">sales.order.fulfillment.v2.tree</field>
+ <field name="model">sales.order.fulfillment.v2</field>
+ <field name="arch" type="xml">
+ <tree editable="top" create="false">
+ <field name="product_id" readonly="1" />
+ <field name="so_qty" readonly="1" optional="show" />
+ <field name="reserved_stock_qty" readonly="1" optional="show" />
+ <field name="delivered_qty" readonly="1" optional="hide" />
+ <field name="po_ids" widget="many2many_tags" readonly="1" optional="show" />
+ <field name="po_qty" readonly="1" optional="show" />
+ <field name="received_qty" readonly="1" optional="show" />
+ <field name="purchaser" readonly="1" optional="hide" />
+ </tree>
+ </field>
+ </record>
<data>
<record id="sales_order_fullfillmet_tree" model="ir.ui.view">
<field name="name">sales.order.fullfillment.tree</field>
<field name="model">sales.order.fullfillment</field>
<field name="arch" type="xml">
<tree editable="top" create="false">
- <field name="product_id" readonly="1"/>
- <field name="reserved_from" readonly="1"/>
- <field name="qty_fullfillment" readonly="1"/>
- <field name="user_id" readonly="1"/>
+ <field name="product_id" readonly="1" />
+ <field name="reserved_from" readonly="1" />
+ <field name="qty_fullfillment" readonly="1" />
+ <field name="user_id" readonly="1" />
</tree>
</field>
</record>
@@ -457,9 +587,9 @@
<field name="model">sales.order.reject</field>
<field name="arch" type="xml">
<tree editable="top" create="false">
- <field name="product_id" readonly="1"/>
- <field name="qty_reject" readonly="1"/>
- <field name="reason_reject" readonly="1"/>
+ <field name="product_id" readonly="1" />
+ <field name="qty_reject" readonly="1" />
+ <field name="reason_reject" readonly="0" />
</tree>
</field>
</record>
@@ -468,8 +598,8 @@
<data>
<record id="sale_order_multi_create_uangmuka_ir_actions_server" model="ir.actions.server">
<field name="name">Uang Muka</field>
- <field name="model_id" ref="sale.model_sale_order"/>
- <field name="binding_model_id" ref="sale.model_sale_order"/>
+ <field name="model_id" ref="sale.model_sale_order" />
+ <field name="binding_model_id" ref="sale.model_sale_order" />
<field name="state">code</field>
<field name="code">action = records.open_form_multi_create_uang_muka()</field>
</record>
@@ -478,66 +608,84 @@
<data>
<record id="mail_template_sale_order_notification_to_salesperson" model="mail.template">
<field name="name">Sale Order: Notification to Salesperson</field>
- <field name="model_id" ref="sale.model_sale_order"/>
+ <field name="model_id" ref="sale.model_sale_order" />
<field name="subject">Konsolidasi Pengiriman</field>
<field name="email_from">sales@indoteknik.com</field>
<field name="email_to">${object.user_id.login | safe}</field>
<field name="body_html" type="html">
- <table border="0" cellpadding="0" cellspacing="0" style="padding-top: 16px; background-color: #F1F1F1; font-family:Inter, Helvetica, Verdana, Arial,sans-serif; line-height: 24px; color: #454748; width: 100%; border-collapse:separate;">
- <tr><td align="center">
- <table border="0" cellpadding="0" cellspacing="0" width="590" style="font-size: 13px; padding: 16px; background-color: white; color: #454748; border-collapse:separate;">
- <!-- HEADER -->
- <tbody>
- <tr>
- <td align="center" style="min-width: 590px;">
- <table border="0" cellpadding="0" cellspacing="0" width="590" style="min-width: 590px; background-color: white; padding: 0px 8px 0px 8px; border-collapse:separate;">
- <tr>
- <td valign="middle">
- <span></span>
- </td>
- </tr>
- </table>
- </td>
- </tr>
- <!-- CONTENT -->
- <tr>
- <td align="center" style="min-width: 590px;">
- <table border="0" cellpadding="0" cellspacing="0" width="590" style="min-width: 590px; background-color: white; padding: 0px 8px 0px 8px; border-collapse:separate;">
- <tr><td style="padding-bottom: 24px;">Dear ${salesperson_name},</td></tr>
-
- <tr><td style="padding-bottom: 16px;">Terdapat pesanan dari BP ${business_partner} untuk site ${site} dengan total belanja ${sum_total_amount} dari list SO dibawah ini:</td></tr>
-
- <tr>
- <td>
- <table border="1" cellpadding="5" cellspacing="0">
- <thead>
- <tr>
- <th>Nama Pesanan</th>
- <th>Nama Perusahaan Induk</th>
- <th>Nama Situs</th>
- <th>Total Pembelian</th>
- </tr>
- </thead>
- <tbody>
- ${table_content}
- </tbody>
- </table>
- </td>
- </tr>
-
- <tr>
- <td style="text-align:center;">
- <hr width="100%"
- style="background-color:rgb(204,204,204);border:medium none;clear:both;display:block;font-size:0px;min-height:1px;line-height:0; margin: 16px 0px 16px 0px;" />
- </td>
- </tr>
- </table>
- </td>
- </tr>
- <!-- CONTENT -->
- </tbody>
- </table>
- </td></tr>
+ <table border="0" cellpadding="0" cellspacing="0"
+ style="padding-top: 16px; background-color: #F1F1F1; font-family:Inter, Helvetica, Verdana, Arial,sans-serif; line-height: 24px; color: #454748; width: 100%; border-collapse:separate;">
+ <tr>
+ <td align="center">
+ <table border="0" cellpadding="0" cellspacing="0" width="590"
+ style="font-size: 13px; padding: 16px; background-color: white; color: #454748; border-collapse:separate;">
+ <!-- HEADER -->
+ <tbody>
+ <tr>
+ <td align="center" style="min-width: 590px;">
+ <table border="0" cellpadding="0" cellspacing="0"
+ width="590"
+ style="min-width: 590px; background-color: white; padding: 0px 8px 0px 8px; border-collapse:separate;">
+ <tr>
+ <td valign="middle">
+ <span></span>
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ <!-- CONTENT -->
+ <tr>
+ <td align="center" style="min-width: 590px;">
+ <table border="0" cellpadding="0" cellspacing="0"
+ width="590"
+ style="min-width: 590px; background-color: white; padding: 0px 8px 0px 8px; border-collapse:separate;">
+ <tr>
+ <td style="padding-bottom: 24px;">Dear
+ ${salesperson_name},</td>
+ </tr>
+
+ <tr>
+ <td style="padding-bottom: 16px;">Terdapat
+ pesanan dari BP ${business_partner} untuk
+ site ${site} dengan total belanja
+ ${sum_total_amount} dari list SO dibawah
+ ini:</td>
+ </tr>
+
+ <tr>
+ <td>
+ <table border="1" cellpadding="5"
+ cellspacing="0">
+ <thead>
+ <tr>
+ <th>Nama Pesanan</th>
+ <th>Nama Perusahaan Induk</th>
+ <th>Nama Situs</th>
+ <th>Total Pembelian</th>
+ </tr>
+ </thead>
+ <tbody>
+ ${table_content}
+ </tbody>
+ </table>
+ </td>
+ </tr>
+
+ <tr>
+ <td style="text-align:center;">
+ <hr width="100%"
+ style="background-color:rgb(204,204,204);border:medium none;clear:both;display:block;font-size:0px;min-height:1px;line-height:0; margin: 16px 0px 16px 0px;" />
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ <!-- CONTENT -->
+ </tbody>
+ </table>
+ </td>
+ </tr>
</table>
</field>
</record>
diff --git a/indoteknik_custom/views/shipment_group.xml b/indoteknik_custom/views/shipment_group.xml
index e9eec41b..d2c661ba 100644
--- a/indoteknik_custom/views/shipment_group.xml
+++ b/indoteknik_custom/views/shipment_group.xml
@@ -7,6 +7,8 @@
<tree default_order="create_date desc">
<field name="number"/>
<field name="partner_id"/>
+ <field name="carrier_id"/>
+ <field name="total_colly_line"/>
</tree>
</field>
</record>
@@ -16,11 +18,9 @@
<field name="model">shipment.group.line</field>
<field name="arch" type="xml">
<tree editable="bottom">
- <field name="picking_id" required="1"/>
- <field name="partner_id" readonly="1"/>
<field name="sale_id" readonly="1"/>
- <field name="shipping_paid_by" readonly="1"/>
- <field name="state" readonly="1"/>
+ <field name="picking_id" required="1"/>
+ <field name="total_colly" readonly="1"/>
</tree>
</field>
</record>
@@ -37,6 +37,8 @@
</group>
<group>
<field name="partner_id" readonly="1"/>
+ <field name="carrier_id" readonly="1"/>
+ <field name="total_colly_line" readonly="1"/>
</group>
</group>
<notebook>
diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml
index 50ea40bf..ae77ab9a 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,8 +19,14 @@
<field name="note" optional="hide"/>
<field name="date_reserved" optional="hide"/>
<field name="state_reserve" optional="hide"/>
+ <field name="state_packing" widget="badge" decoration-success="state_packing == 'packing_done'" decoration-danger="state_packing == 'not_packing'" optional="hide"/>
+ <field name="final_seq"/>
+ <field name="state_approve_md" widget="badge" decoration-success="state_approve_md == 'done'" decoration-warning="state_approve_md == 'pending'" optional="hide"/>
+ <!-- <field name="countdown_hours" optional="hide"/>
+ <field name="countdown_ready_to_ship" /> -->
</field>
<field name="partner_id" position="after">
+ <field name="area_name" optional="hide"/>
<field name="purchase_representative_id"/>
<field name="status_printed"/>
</field>
@@ -66,12 +73,33 @@
type="object"
attrs="{'invisible': [('carrier_id', '!=', 9)]}"
/>
+ <button name="action_get_kgx_pod"
+ string="Tracking KGX"
+ type="object"
+ attrs="{'invisible': [('carrier_id', '!=', 173)]}"
+ />
+ <button name="button_state_approve_md"
+ string="Approve MD Gudang Selisih"
+ type="object"
+ attrs="{'invisible': [('state_approve_md', 'not in', ['waiting', 'pending'])]}"
+ />
+
+ <button name="button_state_pending_md"
+ string="Pending MD Gudang Selisih"
+ type="object"
+ attrs="{'invisible': [('state_approve_md', 'not in', ['waiting'])]}"
+ />
</button>
<field name="backorder_id" position="after">
+ <field name="shipping_method_so_id"/>
<field name="summary_qty_detail"/>
<field name="count_line_detail"/>
<field name="dokumen_tanda_terima"/>
<field name="dokumen_pengiriman"/>
+ <field name="quantity_koli" attrs="{'invisible': [('location_dest_id', '!=', 60)], 'required': [('location_dest_id', '=', 60)]}"/>
+ <field name="total_mapping_koli" attrs="{'invisible': [('location_id', '!=', 60)]}"/>
+ <field name="total_koli_display" readonly="1" attrs="{'invisible': [('location_id', '!=', 60)]}"/>
+ <field name="linked_out_picking_id" readonly="1" attrs="{'invisible': [('location_id', '=', 60)]}"/>
</field>
<field name="weight_uom_name" position="after">
<group>
@@ -93,12 +121,15 @@
<field name="arrival_time"/>
</field>
<field name="origin" position="after">
+<!-- <field name="show_state_approve_md" invisible="1" optional="hide"/>-->
+ <field name="state_approve_md" widget="badge"/>
<field name="purchase_id"/>
<field name="sale_order"/>
<field name="invoice_status"/>
<field name="date_doc_kirim" attrs="{'readonly':[('invoice_status', '=', 'invoiced')]}"/>
<field name="summary_qty_operation"/>
<field name="count_line_operation"/>
+ <field name="linked_manual_bu_out" attrs="{'invisible': [('location_id', '=', 60)]}" domain="[('picking_type_code', '=', 'outgoing'),('state', 'not in', ['done','cancel']), ('group_id', '=', group_id)]"/>
<field name="account_id"
attrs="{
'readonly': [['state', 'in', ['done', 'cancel']]],
@@ -124,6 +155,7 @@
<field name="approval_status"/>
<field name="approval_receipt_status"/>
<field name="approval_return_status"/>
+ <field name="so_lama"/>
</field>
<field name="product_id" position="before">
<field name="line_no" attrs="{'readonly': 1}" optional="hide"/>
@@ -144,13 +176,15 @@
</group>
</group>
</page>
- <page string="Delivery" name="delivery_order">
+ <page string="Delivery" name="delivery_order" attrs="{'invisible': [('location_dest_id', '=', 60)]}">
<group>
<group>
<field name="notee"/>
<field name="note_logistic"/>
+ <field name="note_info"/>
<field name="responsible" />
<field name="carrier_id"/>
+ <field name="out_code" attrs="{'invisible': [['out_code', '=', False]]}"/>
<field name="picking_code" attrs="{'invisible': [['picking_code', '=', False]]}"/>
<field name="picking_code" string="Picking code (akan digenerate ketika sudah di-validate)" attrs="{'invisible': [['picking_code', '!=', False]]}"/>
<field name="driver_departure_date" attrs="{'readonly':[('invoice_status', '=', 'invoiced')]}"/>
@@ -190,27 +224,78 @@
<field name="lalamove_image_url" invisible="1"/>
<field name="lalamove_image_html"/>
</group>
+ <group attrs="{'invisible': [('carrier_id', '!=', 173)]}">
+ <field name="kgx_pod_photo_url" invisible="1"/>
+ <field name="kgx_pod_photo"/>
+ <field name="kgx_pod_signature" invisible="1"/>
+ <field name="kgx_pod_receive_time"/>
+ <field name="kgx_pod_receiver"/>
+ </group>
</group>
</page>
- <page string="Check Product" name="check_product">
+ <page string="Check Product" name="check_product" attrs="{'invisible': [('picking_type_code', '=', 'outgoing')]}">
<field name="check_product_lines"/>
</page>
<page string="Barcode Product" name="barcode_product" attrs="{'invisible': [('picking_type_code', '!=', 'incoming')]}">
<field name="barcode_product_lines"/>
</page>
+ <page string="Check Koli" name="check_koli" attrs="{'invisible': [('location_dest_id', '!=', 60)]}">
+ <field name="check_koli_lines"/>
+ </page>
+ <page string="Mapping Koli" name="konfirm_koli" attrs="{'invisible': [('picking_type_code', '!=', 'outgoing')]}">
+ <field name="konfirm_koli_lines"/>
+ </page>
+ <page string="Konfirm Koli" name="scan_koli" attrs="{'invisible': [('picking_type_code', '!=', 'outgoing')]}">
+ <field name="scan_koli_lines"/>
+ </page>
</page>
</field>
</record>
+ <record id="scan_koli_tree" model="ir.ui.view">
+ <field name="name">scan.koli.tree</field>
+ <field name="model">scan.koli</field>
+ <field name="arch" type="xml">
+ <tree editable="bottom">
+ <field name="code_koli"/>
+ <field name="koli_id" options="{'no_create': True}" domain="[('state', '=', 'not_delivered')]"/>
+ <field name="scan_koli_progress"/>
+ </tree>
+ </field>
+ </record>
+
+ <record id="konfirm_koli_tree" model="ir.ui.view">
+ <field name="name">konfirm.koli.tree</field>
+ <field name="model">konfirm.koli</field>
+ <field name="arch" type="xml">
+ <tree editable="bottom">
+ <field name="pick_id" options="{'no_create': True}" required="1" domain="[('picking_type_code', '=', 'internal'), ('group_id', '=', parent.group_id), ('linked_manual_bu_out', '=', parent.id)]"/>
+ </tree>
+ </field>
+ </record>
+
+ <record id="check_koli_tree" model="ir.ui.view">
+ <field name="name">check.koli.tree</field>
+ <field name="model">check.koli</field>
+ <field name="arch" type="xml">
+ <tree editable="bottom">
+ <field name="koli"/>
+ <field name="reserved_id"/>
+ <field name="check_koli_progress"/>
+ </tree>
+ </field>
+ </record>
+
<record id="check_product_tree" model="ir.ui.view">
<field name="name">check.product.tree</field>
<field name="model">check.product</field>
<field name="arch" type="xml">
<tree editable="bottom" decoration-warning="status == 'Pending'" decoration-success="status == 'Done'">
+ <field name="code_product"/>
<field name="product_id"/>
<field name="quantity"/>
- <field name="status"/>
+ <field name="status" readonly="1"/>
</tree>
</field>
</record>
@@ -247,6 +332,35 @@
<field name="purchase_representative_id"/>
</field>
</field>
- </record>
+ </record>
+
+ <record id="view_warning_modal_wizard_form" model="ir.ui.view">
+ <field name="name">warning.modal.wizard.form</field>
+ <field name="model">warning.modal.wizard</field>
+ <field name="arch" type="xml">
+ <form string="Peringatan Koli Belum Diperiksa">
+ <sheet>
+ <div class="oe_title">
+ <h2><span>⚠️ Perhatian!</span></h2>
+ </div>
+ <group>
+ <field name="message" readonly="1" nolabel="1" widget="text"/>
+ </group>
+ </sheet>
+ <footer>
+ <button name="action_continue" type="object" string="Lanjutkan" class="btn-primary"/>
+ <button string="Tutup" class="btn-secondary" special="cancel"/>
+ </footer>
+ </form>
+ </field>
+ </record>
+
+ <record id="action_warning_modal_wizard" model="ir.actions.act_window">
+ <field name="name">Peringatan Koli</field>
+ <field name="res_model">warning.modal.wizard</field>
+ <field name="view_mode">form</field>
+ <field name="view_id" ref="view_warning_modal_wizard_form"/>
+ <field name="target">new</field>
+ </record>
</data>
</odoo> \ No newline at end of file
diff --git a/indoteknik_custom/views/user_form_merchant.xml b/indoteknik_custom/views/user_form_merchant.xml
new file mode 100644
index 00000000..ae5a0f9f
--- /dev/null
+++ b/indoteknik_custom/views/user_form_merchant.xml
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<odoo>
+ <record id="user_form_merchant_tree" model="ir.ui.view">
+ <field name="name">user.form.merchant.tree</field>
+ <field name="model">user.form.merchant</field>
+ <field name="arch" type="xml">
+ <tree create="1" default_order="create_date desc">
+ <field name="name_merchant"/>
+ <field name="email_company"/>
+ <field name="phone"/>
+ <field name="description"/>
+ <field name="create_date"/>
+ </tree>
+ </field>
+ </record>
+
+ <record id="user_form_merchant_form" model="ir.ui.view">
+ <field name="name">user.form.merchant.form</field>
+ <field name="model">user.form.merchant</field>
+ <field name="arch" type="xml">
+ <form create="0">
+ <sheet>
+ <group>
+ <group string="Informasi Perusahaan" >
+ <field name="name_merchant" />
+ <field name="pejabat_name" />
+ <field name="pic_merchant" />
+ <field name="pic_position" />
+ <field name="address" />
+ <field name="state" />
+ <field name="city" />
+ <field name="district" />
+ <field name="subDistrict" />
+ <field name="zip" />
+ <field name="bank_name" />
+ <field name="rekening_name" />
+ <field name="account_number" />
+ <field name="email_company" widget="email"/>
+ <field name="email_sales" widget="email"/>
+ <field name="email_finance" widget="email"/>
+ <field name="phone" widget="phone"/>
+ <field name="mobile" widget="phone"/>
+ <field name="bisnis_type" />
+ <field name="website" />
+ <field name="category_perusahaan" />
+ </group>
+ </group>
+ <group string="Informasi Vendor">
+ <group>
+ <field name="harga_tayang" />
+ <field name="category_produk_ids" widget="many2many_tags"/>
+ <field name="merk_dagang" />
+ <field name="is_pengajuan_tempo" />
+ <field name="tempo_duration" />
+ <field name="kredit_limit" />
+ <field name="waktu_pengiriman" />
+ <field name="terhitung_sejak" />
+ </group>
+
+ </group>
+ <group string="Syarat Perdagangan">
+ <group>
+ <field name="is_kembali_barang" />
+ <field name="tenggat_waktu" />
+ <field name="sertifikat_produk" />
+<!-- <field name="custom_sertifikat_produk" />-->
+ <field name="tempo_garansi" />
+ <field name="explain_garansi" />
+ <field name="is_order_quantity" />
+
+ </group>
+ </group>
+ <group string="Dokumen">
+ <group>
+ <field name="file_npwp" />
+ <field name="file_sppkp" />
+ <field name="file_dokumenKtpDirut" />
+ <field name="file_kartuNama" />
+ <field name="file_suratPernyataan" />
+ <field name="file_fotoKantor" />
+ <field name="file_dataProduk" />
+ <field name="file_pricelist" />
+ </group>
+ <group>
+<!-- <field name="description" />-->
+ </group>
+ </group>
+ </sheet>
+ </form>
+ </field>
+ </record>
+
+ <record id="action_user_form_merchant" model="ir.actions.act_window">
+ <field name="name">User Form Merchant</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">user.form.merchant</field>
+ <field name="view_mode">tree,form</field>
+ </record>
+
+
+<!-- <menuitem-->
+<!-- id="menu_user_form_merchant"-->
+<!-- name="User Form Merchant"-->
+<!-- parent="res_partner_menu_user"-->
+<!-- sequence="1"-->
+<!-- action="action_user_form_merchant"-->
+<!-- />-->
+
+</odoo> \ No newline at end of file
diff --git a/indoteknik_custom/views/user_merchant_request.xml b/indoteknik_custom/views/user_merchant_request.xml
new file mode 100644
index 00000000..e4f309fd
--- /dev/null
+++ b/indoteknik_custom/views/user_merchant_request.xml
@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<odoo>
+ <record id="user_merchant_request_tree" model="ir.ui.view">
+ <field name="name">user.merchant.request.tree</field>
+ <field name="model">user.merchant.request</field>
+ <field name="arch" type="xml">
+ <tree create="1" default_order="create_date desc">
+ <field name="user_id"/>
+ <field name="merchant_id"/>
+ <field name="state_merchant" decoration-success="state_merchant == 'approved'" decoration-danger="state_merchant == 'reject'" widget="badge" optional="show"/>
+ <field name="create_date"/>
+ </tree>
+ </field>
+ </record>
+
+ <record id="user_merchant_request_form" model="ir.ui.view">
+ <field name="name">user.merchant.request.form</field>
+ <field name="model">user.merchant.request</field>
+ <field name="arch" type="xml">
+ <form create="0">
+ <header>
+ <button name="button_approve"
+ string="Approve Merchant"
+ attrs="{'invisible': [('state_merchant', 'in', ['approved','reject'])]}"
+ type="object"
+ class="oe_highlight"/>
+ <button name="button_reject"
+ string="Reject"
+ attrs="{'invisible': [('state_merchant', 'in', ['approved','reject'])]}"
+ type="object"
+ groups="purchase.group_purchase_manager"
+ class="oe_highlight"/>
+ <field name="state_merchant" widget="statusbar"
+ statusbar_visible="draft,approved"
+ statusbar_colors='{"reject":"red"}'/>
+ </header>
+ <sheet>
+ <group>
+ <group>
+ <field name="user_id" readonly="1"/>
+ <field name="merchant_id"/>
+ </group>
+ </group>
+ </sheet>
+ <div class="oe_chatter">
+ <field name="message_ids" widget="mail_thread"/>
+ </div>
+ </form>
+ </field>
+</record>
+
+
+
+
+ <!-- Wizard for Reject Reason -->
+<record id="view_reject_reason_wizard_merchant_form" model="ir.ui.view">
+ <field name="name">reject.reason.wizard.merchant.form</field>
+ <field name="model">reject.reason.wizard.merchant</field>
+ <field name="arch" type="xml">
+ <form string="Reject Reason">
+ <group>
+ <field name="reason_reject" widget="text"/>
+ </group>
+ <footer>
+ <button string="Confirm" type="object" name="confirm_reject" class="btn-primary"/>
+ <button string="Cancel" class="btn-secondary" special="cancel"/>
+ </footer>
+ </form>
+ </field>
+</record>
+
+<record id="action_reject_reason_wizard_merchant" model="ir.actions.act_window">
+ <field name="name">Reject Reason</field>
+ <field name="res_model">reject.reason.wizard.merchant</field>
+ <field name="view_mode">form</field>
+ <field name="target">new</field>
+</record>
+
+
+<record id="view_confirm_approval_wizard_merchant_form" model="ir.ui.view">
+ <field name="name">confirm.approval.wizard.merchant.form</field>
+ <field name="model">confirm.approval.wizard.merchant</field>
+ <field name="arch" type="xml">
+ <form string="Konfirmasi Approval">
+ <group>
+ <p>Apakah Anda yakin ingin mengapprove merchant ini?</p>
+ </group>
+ <footer>
+ <button string="Batal" class="btn-secondary" special="cancel"/>
+ <button string="Konfirmasi" type="object" name="confirm_approval" class="btn-primary"/>
+ </footer>
+ </form>
+ </field>
+</record>
+
+
+
+ <record id="action_user_merchant_request" model="ir.actions.act_window">
+ <field name="name">User Merchant Request</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">user.merchant.request</field>
+ <field name="view_mode">tree,form</field>
+ </record>
+
+ <menuitem
+ id="menu_user_merchant_request"
+ name="User Merchant Request"
+ parent="res_partner_menu_user"
+ sequence="2"
+ action="action_user_merchant_request"
+ />
+</odoo> \ No newline at end of file
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_payment_term.xml b/indoteknik_custom/views/vendor_payment_term.xml
index e0e96388..7d16b129 100644
--- a/indoteknik_custom/views/vendor_payment_term.xml
+++ b/indoteknik_custom/views/vendor_payment_term.xml
@@ -8,6 +8,8 @@
<field name="display_name"/>
<field name="name"/>
<field name="parent_id"/>
+ <field name="minimum_amount"/>
+ <field name="minimum_amount_tax"/>
<field name="property_supplier_payment_term_id"/>
</tree>
</field>
@@ -23,6 +25,8 @@
<group>
<field name="name"/>
<field name="parent_id" readonly="1"/>
+ <field name="minimum_amount"/>
+ <field name="minimum_amount_tax"/>
<field name="property_supplier_payment_term_id"/>
</group>
</group>
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/voucher.xml b/indoteknik_custom/views/voucher.xml
index ae958f05..78e42969 100755
--- a/indoteknik_custom/views/voucher.xml
+++ b/indoteknik_custom/views/voucher.xml
@@ -27,63 +27,71 @@
<group>
<group>
<field name="image" widget="image" width="120"/>
- <field name="name" required="1" />
- <field name="code" required="1" />
- <field name="visibility" required="1" />
+ <field name="name" required="1"/>
+ <field name="code" required="1"/>
+ <field name="voucher_category" widget="many2many"/>
+ <field name="visibility" required="1"/>
<field name="start_time" required="1"/>
<field name="end_time" required="1"/>
<field name="limit" required="1"/>
<field name="limit_user" required="1"/>
- <field name="apply_type" required="1" />
- <field name="account_type" required="1" />
- <field name="show_on_email" />
- <field name="excl_pricelist_ids" widget="many2many_tags" domain="[('id', 'in', [4, 15037, 15038, 15039, 17023, 17024, 17025, 17026,17027])]"/>
+ <field name="apply_type" required="1"/>
+ <field name="account_type" required="1"/>
+ <field name="show_on_email"/>
+ <field name="excl_pricelist_ids" widget="many2many_tags"
+ domain="[('id', 'in', [4, 15037, 15038, 15039, 17023, 17024, 17025, 17026,17027])]"/>
</group>
- <group string="Discount Settings" attrs="{'invisible': [('apply_type', 'not in', ['all', 'shipping'])]}">
- <field name="min_purchase_amount" widget="monetary" required="1" />
- <field name="discount_type" attrs="{'invisible': [('apply_type','not in', ['all', 'shipping'])], 'required': [('apply_type', 'in', ['all', 'shipping'])]}" />
+ <group string="Discount Settings"
+ attrs="{'invisible': [('apply_type', 'not in', ['all', 'shipping'])]}">
+ <field name="min_purchase_amount" widget="monetary" required="1"/>
+ <field name="discount_type"
+ attrs="{'invisible': [('apply_type','not in', ['all', 'shipping'])], 'required': [('apply_type', 'in', ['all', 'shipping'])]}"/>
- <label for="max_discount_amount" string="Discount Amount" />
+ <label for="max_discount_amount" string="Discount Amount"/>
<div class="d-flex align-items-center">
- <span
- class="mr-1 font-weight-bold"
- attrs="{'invisible': [('discount_type', '!=', 'fixed_price')]}"
+ <span
+ class="mr-1 font-weight-bold"
+ attrs="{'invisible': [('discount_type', '!=', 'fixed_price')]}"
>
Rp
</span>
- <field class="mb-0" name="discount_amount" required="1" />
- <span
- class="ml-1 font-weight-bold"
- attrs="{'invisible': [('discount_type', '!=', 'percentage')]}"
+ <field class="mb-0" name="discount_amount" required="1"/>
+ <span
+ class="ml-1 font-weight-bold"
+ attrs="{'invisible': [('discount_type', '!=', 'percentage')]}"
>
%
</span>
</div>
- <field name="max_discount_amount" widget="monetary" required="1" attrs="{'invisible': [('discount_type', '!=', 'percentage')]}"/>
+ <field name="max_discount_amount" widget="monetary" required="1"
+ attrs="{'invisible': [('discount_type', '!=', 'percentage')]}"/>
</group>
</group>
<notebook>
- <page name="voucher_line" string="Voucher Line" attrs="{'invisible': [('apply_type', '!=', 'brand')]}">
+ <page name="voucher_line" string="Voucher Line"
+ attrs="{'invisible': [('apply_type', '!=', 'brand')]}">
<field name="voucher_line">
<tree editable="bottom">
- <field name="manufacture_id" required="1" />
- <field name="min_purchase_amount" required="1" />
- <field name="discount_type" required="1" />
- <field name="discount_amount" required="1" />
- <field name="max_discount_amount" required="1" attrs="{'readonly': [('discount_type', '!=', 'percentage')]}" />
+ <field name="manufacture_id" required="1"/>
+ <field name="min_purchase_amount" required="1"/>
+ <field name="discount_type" required="1"/>
+ <field name="discount_amount" required="1"/>
+ <field name="max_discount_amount" required="1"
+ attrs="{'readonly': [('discount_type', '!=', 'percentage')]}"/>
</tree>
</field>
</page>
<page name="description" string="Description">
- <label for="description" string="Max 120 characters:" class="font-weight-normal mb-2 oe_edit_only"/>
- <field name="description" placeholder="Insert short description..." />
+ <label for="description" string="Max 120 characters:"
+ class="font-weight-normal mb-2 oe_edit_only"/>
+ <field name="description" placeholder="Insert short description..."/>
</page>
<page name="terms_conditions" string="Terms and Conditions">
- <field name="terms_conditions" />
+ <field name="terms_conditions"/>
</page>
<page name="order_page" string="Orders">
- <field name="order_ids" readonly="1" />
+ <field name="order_ids" readonly="1"/>
</page>
</notebook>
</sheet>
@@ -92,10 +100,10 @@
</record>
<menuitem id="voucher"
- name="Voucher"
- parent="website_sale.menu_catalog"
- sequence="1"
- action="voucher_action"
+ name="Voucher"
+ parent="website_sale.menu_catalog"
+ sequence="1"
+ action="voucher_action"
/>
</data>
</odoo> \ No newline at end of file
diff --git a/indoteknik_custom/views/x_banner_banner.xml b/indoteknik_custom/views/x_banner_banner.xml
index ec1e38a5..e40568cc 100755
--- a/indoteknik_custom/views/x_banner_banner.xml
+++ b/indoteknik_custom/views/x_banner_banner.xml
@@ -33,6 +33,7 @@
<field name="group_by_week" />
<field name="x_headline_banner" />
<field name="x_description_banner" />
+ <field name="x_keyword_banner" />
<field name="last_update_solr" readonly="1"/>
</group>
<group>
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>