summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorit-fixcomart <it@fixcomart.co.id>2025-05-14 09:23:14 +0700
committerit-fixcomart <it@fixcomart.co.id>2025-05-14 09:23:14 +0700
commitf120c760c6a837681ebed26d9eea33a8961cd1aa (patch)
tree728ecdd5ffd1530d97d58a9f18b850186b39aa67
parenta571531bd8626f9bee25e89c62bbd9268ed30597 (diff)
parent2469ee37cfe854f0419a8c3fbabed5bc32bcaa6e (diff)
Merge branch 'odoo-backup' into CR/form-merchant
# Conflicts: # indoteknik_custom/models/__init__.py # indoteknik_custom/security/ir.model.access.csv
-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/partner.py590
-rw-r--r--indoteknik_api/controllers/api_v1/product.py87
-rw-r--r--indoteknik_api/controllers/api_v1/sale_order.py13
-rw-r--r--indoteknik_api/controllers/api_v1/stock_picking.py61
-rw-r--r--indoteknik_api/controllers/api_v1/user.py31
-rw-r--r--indoteknik_api/controllers/api_v1/voucher.py22
-rw-r--r--indoteknik_api/models/product_pricelist.py12
-rw-r--r--indoteknik_api/models/res_partner.py8
-rwxr-xr-xindoteknik_custom/__manifest__.py8
-rw-r--r--indoteknik_custom/controllers/website.py7
-rwxr-xr-xindoteknik_custom/models/__init__.py9
-rw-r--r--indoteknik_custom/models/account_move.py28
-rw-r--r--indoteknik_custom/models/account_move_line.py9
-rw-r--r--indoteknik_custom/models/account_payment_register.py48
-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.py204
-rw-r--r--indoteknik_custom/models/barcoding_product.py62
-rw-r--r--indoteknik_custom/models/commision.py288
-rw-r--r--indoteknik_custom/models/coretax_fatur.py3
-rw-r--r--indoteknik_custom/models/delivery_order.py7
-rw-r--r--indoteknik_custom/models/invoice_reklas.py10
-rw-r--r--indoteknik_custom/models/invoice_reklas_penjualan.py2
-rw-r--r--indoteknik_custom/models/ir_actions_report.py8
-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.py72
-rw-r--r--indoteknik_custom/models/public_holiday.py11
-rwxr-xr-xindoteknik_custom/models/purchase_order.py159
-rwxr-xr-xindoteknik_custom/models/purchase_order_line.py60
-rw-r--r--indoteknik_custom/models/purchase_order_multi_ask_approval.py22
-rw-r--r--indoteknik_custom/models/purchase_order_multi_uangmuka.py2
-rw-r--r--indoteknik_custom/models/purchase_order_sales_match.py13
-rwxr-xr-xindoteknik_custom/models/purchase_pricelist.py11
-rw-r--r--indoteknik_custom/models/purchasing_job.py15
-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/report_logbook_sj.py2
-rw-r--r--indoteknik_custom/models/requisition.py42
-rw-r--r--indoteknik_custom/models/res_partner.py70
-rwxr-xr-xindoteknik_custom/models/res_users.py4
-rwxr-xr-xindoteknik_custom/models/sale_order.py639
-rw-r--r--indoteknik_custom/models/sale_order_line.py19
-rw-r--r--indoteknik_custom/models/sale_order_multi_uangmuka_penjualan.py2
-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.py16
-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.py9
-rw-r--r--indoteknik_custom/models/stock_inventory.py79
-rw-r--r--indoteknik_custom/models/stock_move.py35
-rw-r--r--indoteknik_custom/models/stock_picking.py1497
-rw-r--r--indoteknik_custom/models/stock_picking_return.py13
-rw-r--r--indoteknik_custom/models/uangmuka_pembelian.py2
-rw-r--r--indoteknik_custom/models/uangmuka_penjualan.py2
-rw-r--r--indoteknik_custom/models/user_company_request.py6
-rw-r--r--indoteknik_custom/models/user_pengajuan_tempo.py3
-rw-r--r--indoteknik_custom/models/user_pengajuan_tempo_request.py93
-rw-r--r--indoteknik_custom/models/vendor_sla.py102
-rw-r--r--indoteknik_custom/models/voucher.py274
-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.csv23
-rw-r--r--indoteknik_custom/views/account_move.xml5
-rw-r--r--indoteknik_custom/views/approval_invoice_date.xml92
-rw-r--r--indoteknik_custom/views/barcoding_product.xml77
-rw-r--r--indoteknik_custom/views/customer_commision.xml238
-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.xml25
-rw-r--r--indoteknik_custom/views/purchase_order_multi_ask_approval.xml31
-rw-r--r--indoteknik_custom/views/purchasing_job.xml1
-rw-r--r--indoteknik_custom/views/report_logbook_sj.xml10
-rw-r--r--indoteknik_custom/views/res_partner.xml3
-rw-r--r--indoteknik_custom/views/res_users.xml2
-rwxr-xr-xindoteknik_custom/views/sale_order.xml703
-rw-r--r--indoteknik_custom/views/shipment_group.xml10
-rw-r--r--indoteknik_custom/views/stock_backorder_confirmation_views.xml14
-rw-r--r--indoteknik_custom/views/stock_inventory.xml28
-rw-r--r--indoteknik_custom/views/stock_picking.xml149
-rw-r--r--indoteknik_custom/views/user_pengajuan_tempo.xml4
-rw-r--r--indoteknik_custom/views/user_pengajuan_tempo_request.xml21
-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.xml73
-rwxr-xr-xindoteknik_custom/views/x_banner_banner.xml1
-rwxr-xr-xindoteknik_custom/views/x_banner_category.xml2
101 files changed, 6161 insertions, 1317 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/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 8b95ade8..98b13cad 100644
--- a/indoteknik_api/controllers/api_v1/sale_order.py
+++ b/indoteknik_api/controllers/api_v1/sale_order.py
@@ -386,7 +386,8 @@ class SaleOrder(controller.Controller):
'note_website': [],
'voucher': [],
'source': [],
- 'estimated_arrival_days': ['number', 'default:0']
+ 'estimated_arrival_days': ['number', 'default:0'],
+ 'estimated_arrival_days_start': ['number', 'default:0']
})
if not params['valid']:
@@ -394,7 +395,8 @@ class SaleOrder(controller.Controller):
# Fetch partner details
sales_partner = request.env['res.partner'].browse(params['value']['partner_id'])
-
+ partner_invoice = request.env['res.partner'].browse(params['value']['partner_invoice_id'])
+ main_partner = partner_invoice.get_main_parent()
parameters = {
'warehouse_id': 8,
'carrier_id': 1,
@@ -410,12 +412,13 @@ class SaleOrder(controller.Controller):
'partner_id': params['value']['partner_id'],
'partner_shipping_id': params['value']['partner_shipping_id'],
'real_shipping_id': params['value']['partner_shipping_id'],
- 'partner_invoice_id': params['value']['partner_invoice_id'],
+ 'partner_invoice_id': main_partner.id,
'real_invoice_id': params['value']['partner_invoice_id'],
'partner_purchase_order_name': params['value']['po_number'],
'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'],
@@ -426,7 +429,7 @@ class SaleOrder(controller.Controller):
'npwp': sales_partner.npwp or '0', # Get NPWP from partner
'sppkp': sales_partner.sppkp, # Get SPPKP from partner
'email': sales_partner.email, # Get Email from partner
- 'user_id': 3222 # User ID: Nadia Rauhadatul Firdaus
+ 'user_id': 11314 # User ID: Boy Revandi
}
sales_partner = request.env['res.partner'].browse(parameters['partner_id'])
@@ -463,6 +466,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 110cde8a..31706b99 100644
--- a/indoteknik_api/controllers/api_v1/stock_picking.py
+++ b/indoteknik_api/controllers/api_v1/stock_picking.py
@@ -57,7 +57,7 @@ class StockPicking(controller.Controller):
if params['status'] == 'pending':
domain += pending_domain
elif params['status'] == 'shipment':
- domain += shipment_domain
+ domain += shipment_domain + shipment_domain2
elif params['status'] == 'completed':
domain += completed_domain
@@ -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 f71af89f..b5b7e055 100644
--- a/indoteknik_api/controllers/api_v1/user.py
+++ b/indoteknik_api/controllers/api_v1/user.py
@@ -98,7 +98,7 @@ class User(controller.Controller):
user.partner_id.npwp = '00.000.000.0-000.000'
user.partner_id.sppkp = '-'
user.partner_id.nama_wajib_pajak = user.name
- user.partner_id.user_id = 3222
+ user.partner_id.user_id = 11314
user.partner_id.property_account_receivable_id = 395
user.partner_id.property_account_payable_id = 438
data = {
@@ -131,6 +131,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,16 +163,15 @@ 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
query = """
SELECT id, name, levenshtein(name::text, %s) AS distance
FROM res_partner
- WHERE levenshtein(name::text, %s) < 3
+ WHERE is_company = true AND active = true
+ AND levenshtein(name::text, %s) < 3
ORDER BY distance ASC
"""
params = (business_name, business_name)
@@ -181,7 +181,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,
@@ -189,6 +191,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
@@ -207,11 +212,15 @@ class User(controller.Controller):
'email': email_partner,
'street': alamat_bisnis,
'company_type': 'company',
- 'user_id': 3222,
+ 'user_id': 11314,
'property_account_receivable_id': 395,
'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,
@@ -246,13 +255,15 @@ 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 = '-'
user.partner_id.nama_wajib_pajak = name
- user.partner_id.user_id = 3222
+ user.partner_id.user_id = 11314
user.partner_id.property_account_receivable_id= 395
user.partner_id.property_account_payable_id = 438
@@ -604,7 +615,7 @@ class User(controller.Controller):
'email': email_partner,
'street': alamat_bisnis,
'company_type': 'company',
- 'user_id': 3222,
+ 'user_id': 11314,
'property_account_receivable_id': 395,
'property_account_payable_id': 438,
'active': False,
diff --git a/indoteknik_api/controllers/api_v1/voucher.py b/indoteknik_api/controllers/api_v1/voucher.py
index 910488d1..9ffeeace 100644
--- a/indoteknik_api/controllers/api_v1/voucher.py
+++ b/indoteknik_api/controllers/api_v1/voucher.py
@@ -22,9 +22,14 @@ class Voucher(controller.Controller):
code = kw.get('code')
type = kw.get('type')
user_id = int(kw.get('user_id', 0))
+ partner_id = int(kw.get('partner_id', 0))
source = kw.get('source')
visibility = ['public']
+ user = request.env['res.users'].search([('id', '=', user_id)], limit=1)
+ if not user:
+ return self.response([])
+
domain = []
if code:
visibility.append('private')
@@ -37,14 +42,19 @@ class Voucher(controller.Controller):
type = type.split(',')
domain += [('apply_type', 'in', type)]
+ if partner_id:
+ partner = request.env['res.partner'].search([('id', '=', partner_id)], limit=1)
+ main_parent = partner.get_main_parent()
+ if main_parent and main_parent.company_type:
+ domain += [('account_type', 'in', ['all',main_parent.company_type])]
+ # domain += [('account_type', 'in', main_parent.company_type)]
+
+
domain += [('visibility', 'in', visibility)]
vouchers = request.env['voucher'].get_active_voucher(domain)
checkout = cart.get_user_checkout(user_id, source=source)
products = checkout['products']
- user = request.env['res.users'].search([('id', '=', user_id)], limit=1)
- if not user:
- return self.response([])
order_line = []
for product in products:
@@ -89,3 +99,9 @@ class Voucher(controller.Controller):
sorted_results = sorted(results, key=lambda x: x['can_apply'], reverse=True)
return self.response(sorted_results)
+
+ def get_user_by_email(self, email):
+ return request.env['res.users'].search([
+ ('login', '=', email),
+ ('active', 'in', [True, False])
+ ]) \ No newline at end of file
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 5569a877..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,
diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py
index a63d7c89..d93db041 100755
--- a/indoteknik_custom/__manifest__.py
+++ b/indoteknik_custom/__manifest__.py
@@ -120,6 +120,7 @@
'views/sale_monitoring_detail_v2.xml',
'views/purchase_order_multi_update.xml',
'views/purchase_order_multi_confirm.xml',
+ 'views/purchase_order_multi_ask_approval.xml',
'views/invoice_reklas_penjualan.xml',
'views/po_multi_cancel.xml',
'views/logbook_sj.xml',
@@ -144,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',
@@ -153,6 +155,9 @@
'views/form_vendor_approval_multi_approve.xml',
'views/form_vendor_approval_multi_reject.xml',
'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',
@@ -160,7 +165,10 @@
'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': [],
'css': [],
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 714d29d3..e17f68d1 100755
--- a/indoteknik_custom/models/__init__.py
+++ b/indoteknik_custom/models/__init__.py
@@ -106,6 +106,7 @@ from . import sale_monitoring_detail_v2
from . import purchase_order_multi_update
from . import invoice_reklas_penjualan
from . import purchase_order_multi_confirm
+from . import purchase_order_multi_ask_approval
from . import po_multi_cancel
from . import logbook_sj
from . import report_logbook_sj
@@ -138,8 +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 85ed1d54..30de67be 100644
--- a/indoteknik_custom/models/account_move.py
+++ b/indoteknik_custom/models/account_move.py
@@ -64,14 +64,37 @@ class AccountMove(models.Model):
nomor_kwitansi = fields.Char(string="Nomor Kwitansi")
other_subtotal = fields.Float(string="Other Subtotal", compute='compute_other_subtotal')
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"""
+ for move in self:
+ if move.move_type == 'entry' and move.ref and move.line_ids:
+ for line in move.line_ids:
+ line.name = move.ref
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):
@@ -107,6 +130,7 @@ class AccountMove(models.Model):
def create(self, vals):
vals['nomor_kwitansi'] = self.env['ir.sequence'].next_by_code('nomor.kwitansi') or '0'
result = super(AccountMove, self).create(vals)
+ # result._update_line_name_from_ref()
return result
def compute_so_shipping_paid_by(self):
diff --git a/indoteknik_custom/models/account_move_line.py b/indoteknik_custom/models/account_move_line.py
index a4b25109..7c95d4ef 100644
--- a/indoteknik_custom/models/account_move_line.py
+++ b/indoteknik_custom/models/account_move_line.py
@@ -7,6 +7,7 @@ class AccountMoveLine(models.Model):
cost_centre_id = fields.Many2one('cost.centre', string='Cost Centre')
is_required = fields.Boolean(string='Is Required', compute='_compute_is_required')
analytic_account_ids = fields.Many2many('account.analytic.account', string='Analytic Account')
+ line_no = fields.Integer('No', default=0)
@api.onchange('account_id')
def _onchange_account_id(self):
@@ -22,3 +23,11 @@ class AccountMoveLine(models.Model):
else:
account.is_required = False
+ @api.model_create_multi
+ def create(self, vals_list):
+ for vals in vals_list:
+ if 'move_id' in vals:
+ move = self.env['account.move'].browse(vals['move_id'])
+ if move.move_type == 'entry' and move.is_hr == True:
+ vals['name'] = move.ref
+ return super().create(vals_list) \ No newline at end of file
diff --git a/indoteknik_custom/models/account_payment_register.py b/indoteknik_custom/models/account_payment_register.py
new file mode 100644
index 00000000..4ebb7e4e
--- /dev/null
+++ b/indoteknik_custom/models/account_payment_register.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+
+from odoo import models, fields, api, _
+from odoo.exceptions import UserError
+
+
+class AccountPaymentRegister(models.TransientModel):
+ _inherit = 'account.payment.register'
+ def _create_payment_vals_from_wizard(self):
+ payment_vals = {
+ 'date': self.payment_date,
+ 'amount': self.amount,
+ 'payment_type': self.payment_type,
+ 'partner_type': self.partner_type,
+ 'ref': self.communication,
+ 'journal_id': self.journal_id.id,
+ 'is_hr': True,
+ 'currency_id': self.currency_id.id,
+ 'partner_id': self.partner_id.id,
+ 'partner_bank_id': self.partner_bank_id.id,
+ 'payment_method_id': self.payment_method_id.id,
+ 'destination_account_id': self.line_ids[0].account_id.id
+ }
+
+ if not self.currency_id.is_zero(self.payment_difference) and self.payment_difference_handling == 'reconcile':
+ payment_vals['write_off_line_vals'] = {
+ 'name': self.writeoff_label,
+ 'amount': self.payment_difference,
+ 'account_id': self.writeoff_account_id.id,
+ }
+ return payment_vals
+
+ def _create_payment_vals_from_batch(self, batch_result):
+ batch_values = self._get_wizard_values_from_batch(batch_result)
+ return {
+ 'date': self.payment_date,
+ 'amount': batch_values['source_amount_currency'],
+ 'payment_type': batch_values['payment_type'],
+ 'partner_type': batch_values['partner_type'],
+ 'ref': self._get_batch_communication(batch_result),
+ 'journal_id': self.journal_id.id,
+ 'is_hr': True,
+ 'currency_id': batch_values['source_currency_id'],
+ 'partner_id': batch_values['partner_id'],
+ 'partner_bank_id': batch_result['key_values']['partner_bank_id'],
+ 'payment_method_id': self.payment_method_id.id,
+ 'destination_account_id': batch_result['lines'][0].account_id.id
+ }
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 09d283eb..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'],
@@ -183,89 +193,94 @@ class AutomaticPurchase(models.Model):
def create_po_by_vendor(self, vendor_id):
current_time = datetime.now()
- if not self.apo_type =='reordering':
- name = "/PJ/"
- else:
- name = "/A/"
+ name = "/PJ/" if not self.apo_type == 'reordering' else "/A/"
PRODUCT_PER_PO = 20
-
auto_purchase_line = self.env['automatic.purchase.line']
- last_po = self.env['purchase.order'].search([
- ('partner_id', '=', vendor_id),
- ('state', '=', 'done'),
- ], order='id desc', limit=1)
-
- param_header = {
- 'partner_id': vendor_id,
- '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,
- 'from_apo': True,
- 'note_description': 'Automatic PO'
- }
-
+ # Domain untuk semua baris dengan vendor_id tertentu
domain = [
('automatic_purchase_id', '=', self.id),
('partner_id', '=', vendor_id),
('qty_purchase', '>', 0)
]
- products_len = auto_purchase_line.search_count(domain)
- page = math.ceil(products_len / PRODUCT_PER_PO)
-
- # i start from zero (0)
- for i in range(page):
- new_po = self.env['purchase.order'].create([param_header])
- new_po.payment_term_id = new_po.partner_id.property_supplier_payment_term_id
- new_po.name = new_po.name + name + str(i + 1)
+ # Tambahkan domain khusus untuk brand_id 22 dan 564
+ special_brand_domain = domain + [('brand_id', 'in', [22, 564])]
+ regular_domain = domain + [('brand_id', 'not in', [22, 564])]
+
+ # Fungsi untuk membuat PO berdasarkan domain tertentu
+ def create_po_for_domain(domain, special_payment_term=False):
+ products_len = auto_purchase_line.search_count(domain)
+ page = math.ceil(products_len / PRODUCT_PER_PO)
+
+ for i in range(page):
+ # Buat PO baru
+ param_header = {
+ 'partner_id': vendor_id,
+ '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,
+ 'from_apo': True,
+ 'note_description': 'Automatic PO'
+ }
- self.env['automatic.purchase.match'].create([{
- 'automatic_purchase_id': self.id,
- 'order_id': new_po.id
- }])
+ new_po = self.env['purchase.order'].create([param_header])
- lines = auto_purchase_line.search(
- domain,
- offset=i * PRODUCT_PER_PO,
- limit=PRODUCT_PER_PO
- )
+ # Set payment_term_id khusus jika diperlukan
+ if special_payment_term:
+ new_po.payment_term_id = 29
+ else:
+ new_po.payment_term_id = new_po.partner_id.property_supplier_payment_term_id
- lines = auto_purchase_line.search(
- domain,
- offset=i * PRODUCT_PER_PO,
- limit=PRODUCT_PER_PO
- )
+ new_po.name = new_po.name + name + str(i + 1)
+ self.env['automatic.purchase.match'].create([{
+ 'automatic_purchase_id': self.id,
+ 'order_id': new_po.id
+ }])
+
+ # Ambil baris sesuai halaman
+ lines = auto_purchase_line.search(
+ domain,
+ offset=i * PRODUCT_PER_PO,
+ limit=PRODUCT_PER_PO
+ )
+
+ for line in lines:
+ product = line.product_id
+ sales_match = self.env['automatic.purchase.sales.match'].search([
+ ('automatic_purchase_id', '=', self.id),
+ ('product_id', '=', product.id),
+ ])
+ param_line = {
+ 'order_id': new_po.id,
+ 'product_id': product.id,
+ 'product_qty': line.qty_purchase,
+ 'qty_available_store': product.qty_available_bandengan,
+ 'suggest': product._get_po_suggest(line.qty_purchase),
+ 'product_uom_qty': line.qty_purchase,
+ 'price_unit': line.last_price,
+ 'ending_price': line.last_price,
+ 'taxes_id': [line.taxes_id.id] if line.taxes_id else None,
+ 'so_line_id': sales_match[0].sale_line_id.id if sales_match else None,
+ 'so_id': sales_match[0].sale_id.id if sales_match else None
+ }
+ new_po_line = self.env['purchase.order.line'].create([param_line])
+ line.current_po_id = new_po.id
+ line.current_po_line_id = new_po_line.id
+
+ self.create_purchase_order_sales_match(new_po)
+
+ # Buat PO untuk special brand
+ if vendor_id == 23:
+ create_po_for_domain(special_brand_domain, special_payment_term=True)
+
+ # Buat PO untuk regular domain
+ create_po_for_domain(regular_domain, "")
- for line in lines:
- product = line.product_id
- sales_match = self.env['automatic.purchase.sales.match'].search([
- ('automatic_purchase_id', '=', self.id),
- ('product_id', '=', product.id),
- ])
- param_line = {
- 'order_id': new_po.id,
- 'product_id': product.id,
- 'product_qty': line.qty_purchase,
- 'qty_available_store': product.qty_available_bandengan,
- 'suggest': product._get_po_suggest(line.qty_purchase),
- 'product_uom_qty': line.qty_purchase,
- 'price_unit': line.last_price,
- 'ending_price': line.last_price,
- 'taxes_id': [line.taxes_id.id] if line.taxes_id else None,
- 'so_line_id': sales_match[0].sale_line_id.id if sales_match else None,
- 'so_id': sales_match[0].sale_id.id if sales_match else None
- }
- new_po_line = self.env['purchase.order.line'].create([param_line])
- line.current_po_id = new_po.id
- line.current_po_line_id = new_po_line.id
- # self.update_purchase_price_so_line(line)
-
- self.create_purchase_order_sales_match(new_po)
def update_purchase_price_so_line(self, apo):
sales_match = self.env['automatic.purchase.sales.match'].search([
@@ -279,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),
])
@@ -287,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}"
@@ -650,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
new file mode 100644
index 00000000..204c6128
--- /dev/null
+++ b/indoteknik_custom/models/barcoding_product.py
@@ -0,0 +1,62 @@
+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 BarcodingProduct(models.Model):
+ _name = "barcoding.product"
+ _description = "Barcoding Product"
+
+ 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'), ('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:
+ 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"""
+ if self.product_id and self.quantity > 0:
+ # Clear existing lines
+ self.barcoding_product_line = [(5, 0, 0)]
+
+ # Add a new line with the current product and quantity
+ self.barcoding_product_line = [(0, 0, {
+ 'product_id': self.product_id.id,
+ 'barcoding_product_id': self.id,
+ }) for _ in range(int(self.quantity))]
+
+
+class BarcodingProductLine(models.Model):
+ _name = 'barcoding.product.line'
+ _description = 'Barcoding Product Line'
+ _order = 'barcoding_product_id, id'
+
+ barcoding_product_id = fields.Many2one('barcoding.product', string='Barcoding Product Ref', required=True, ondelete='cascade', index=True, copy=False)
+ product_id = fields.Many2one('product.product', string="Product")
+ qr_code_variant = fields.Binary("QR Code Variant", related='product_id.qr_code_variant') \ No newline at end of file
diff --git a/indoteknik_custom/models/commision.py b/indoteknik_custom/models/commision.py
index 48f1c7f6..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,25 +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?
@@ -178,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
@@ -215,23 +346,72 @@ 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')
+
+ if self.payment_status == 'payment':
+ raise UserError('Customer Commision sudah berstatus Payment')
+ group_id = self.env.ref('indoteknik_custom.group_role_fat').id
+ users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])])
+ if self.env.user.id not in users_in_group.mapped('id'):
+ raise UserError('Hanya bisa dikonfirmasi oleh FAT')
+ else:
+ self.payment_status = 'payment'
+ return
+
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:
@@ -306,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')
@@ -318,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/coretax_fatur.py b/indoteknik_custom/models/coretax_fatur.py
index ae6dd2ae..b4bffbd2 100644
--- a/indoteknik_custom/models/coretax_fatur.py
+++ b/indoteknik_custom/models/coretax_fatur.py
@@ -59,6 +59,7 @@ class CoretaxFaktur(models.Model):
ET.SubElement(tax_invoice, 'TrxCode').text = '04'
ET.SubElement(tax_invoice, 'AddInfo')
ET.SubElement(tax_invoice, 'CustomDoc')
+ ET.SubElement(tax_invoice, 'CustomDocMonthYear')
ET.SubElement(tax_invoice, 'RefDesc').text = invoice.name
ET.SubElement(tax_invoice, 'FacilityStamp')
ET.SubElement(tax_invoice, 'SellerIDTKU').text = '0742260227086000000000'
@@ -77,7 +78,7 @@ class CoretaxFaktur(models.Model):
otherTaxBase = round(line.price_subtotal * (11/12)) if line.price_subtotal else 0
good_service = ET.SubElement(list_of_good_service, 'GoodService')
ET.SubElement(good_service, 'Opt').text = 'A'
- ET.SubElement(good_service, 'Code')
+ ET.SubElement(good_service, 'Code').text = '000000'
ET.SubElement(good_service, 'Name').text = line.name
ET.SubElement(good_service, 'Unit').text = 'UM.0018'
ET.SubElement(good_service, 'Price').text = str(round(line.price_subtotal/line.quantity, 2)) if line.price_subtotal else '0'
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 30da02d1..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')
@@ -49,7 +55,7 @@ class InvoiceReklas(models.TransientModel):
if self.reklas_type == 'penjualan':
parameter_debit = {
'move_id': account_move.id,
- 'account_id': 449, # uang muka penjualan
+ 'account_id': 668, # penerimaan belum alokasi
'partner_id': invoice.partner_id.id,
'currency_id': 12,
'debit': self.pay_amt,
@@ -77,7 +83,7 @@ class InvoiceReklas(models.TransientModel):
}
parameter_credit = {
'move_id': account_move.id,
- 'account_id': 401,
+ 'account_id': 669,
'partner_id': invoice.partner_id.id,
'currency_id': 12,
'debit': 0,
diff --git a/indoteknik_custom/models/invoice_reklas_penjualan.py b/indoteknik_custom/models/invoice_reklas_penjualan.py
index 5027c8af..80c3ed43 100644
--- a/indoteknik_custom/models/invoice_reklas_penjualan.py
+++ b/indoteknik_custom/models/invoice_reklas_penjualan.py
@@ -33,7 +33,7 @@ class InvoiceReklasPenjualan(models.TransientModel):
parameter_debit = {
'move_id': account_move.id,
- 'account_id': 449, # uang muka penjualan
+ 'account_id': 668, # uang muka penjualan
'partner_id': invoice.partner_id.id,
'currency_id': 12,
'debit': invoice.pay_amt,
diff --git a/indoteknik_custom/models/ir_actions_report.py b/indoteknik_custom/models/ir_actions_report.py
index 28adcf74..83636945 100644
--- a/indoteknik_custom/models/ir_actions_report.py
+++ b/indoteknik_custom/models/ir_actions_report.py
@@ -17,13 +17,13 @@ class IrActionsReport(models.Model):
return
# ci_vita 7751529082:AAE9XsZa_Pj2Pi2IN1grX98WkwTaIt32pbI & 5081411103
# iman 7094158106:AAHpWtYOMnA3Yqm_Fvrr3Vw7MrB45vLV9AY & 6592318498
- # bot_name_iman = '7094158106:AAHpWtYOMnA3Yqm_Fvrr3Vw7MrB45vLV9AY'
- # chat_id_iman = '6592318498'
+ bot_name_iman = '7094158106:AAHpWtYOMnA3Yqm_Fvrr3Vw7MrB45vLV9AY'
+ chat_id_iman = '-1002493002821'
bot_name_vita = '7751529082:AAE9XsZa_Pj2Pi2IN1grX98WkwTaIt32pbI'
chat_id_vita = '5081411103'
- apiURL = f'https://api.telegram.org/bot{bot_name_vita}/sendMessage'
+ apiURL = f'https://api.telegram.org/bot{bot_name_iman}/sendMessage'
try:
- requests.post(apiURL, json={'chat_id': chat_id_vita, 'text': sale_order.name + " senilai Rp" + self.format_currency(sale_order.amount_total) + ' untuk customer ' + sale_order.partner_id.name + ' telah dibuat oleh sales ' +sale_order.user_id.name})
+ requests.post(apiURL, json={'chat_id': chat_id_iman, 'text': sale_order.name + " senilai Rp" + self.format_currency(sale_order.amount_total) + ' untuk customer ' + sale_order.partner_id.name + ' telah dibuat oleh sales ' +sale_order.user_id.name})
except Exception as e:
print(e)
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 5bedae13..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",
@@ -67,6 +75,25 @@ class ProductTemplate(models.Model):
print_barcode = fields.Boolean(string='Print Barcode', default=True)
# qr_code = fields.Binary("QR Code", compute='_compute_qr_code')
+ @api.model
+ def create(self, vals):
+ 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 not in users_in_group.mapped('id') and active_model == None:
+ raise UserError('Hanya MD yang bisa membuat Product')
+ result = super(ProductTemplate, self).create(vals)
+ return result
+
+ # def write(self, values):
+ # group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id
+ # users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])])
+ # active_model = self.env.context.get('active_model')
+ # if self.env.user.id not in users_in_group.mapped('id') and active_model == None:
+ # raise UserError('Hanya MD yang bisa mengedit Product')
+ # result = super(ProductTemplate, self).write(values)
+ # return result
+
# def _compute_qr_code(self):
# for rec in self.product_variant_ids:
# qr = qrcode.QRCode(
@@ -227,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:
@@ -402,6 +429,39 @@ 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
+ active_model = self.env.context.get('active_model')
+ users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])])
+ if self.env.user.id not in users_in_group.mapped('id') and active_model == None:
+ raise UserError('Hanya MD yang bisa membuat Product')
+ result = super(ProductProduct, self).create(vals)
+ return result
+
+ # def write(self, values):
+ # group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id
+ # active_model = self.env.context.get('active_model')
+ # users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])])
+ # if self.env.user.id not in users_in_group.mapped('id') and active_model == None:
+ # raise UserError('Hanya MD yang bisa mengedit Product')
+ # result = super(ProductProduct, self).write(values)
+ # return result
def _compute_qr_code_variant(self):
for rec in self:
@@ -416,7 +476,7 @@ class ProductProduct(models.Model):
box_size=5,
border=4,
)
- qr.add_data(rec.default_code)
+ qr.add_data(rec.barcode if rec.barcode else rec.default_code)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
@@ -682,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 d487ada3..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')
@@ -86,6 +87,113 @@ class PurchaseOrder(models.Model):
total_cost_service = fields.Float(string='Total Cost Service')
total_delivery_amt = fields.Float(string='Total Delivery Amt')
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):
@@ -154,6 +262,7 @@ class PurchaseOrder(models.Model):
'invoice_date': current_date,
'date': current_date,
'invoice_origin': self.name,
+ 'purchase_order_id': self.id,
'move_type': 'in_invoice'
}
@@ -178,7 +287,7 @@ class PurchaseOrder(models.Model):
'move_id': bills.id,
'product_id': product_dp.id, # product down payment
'name': '[IT.121456] Down Payment', # product down payment
- 'account_id': 401, # Uang Muka persediaan barang dagang
+ 'account_id': 669, # Uang Muka persediaan barang dagang
# 'price_unit': move_line.price_unit,
'quantity': -1,
'product_uom_id': 1,
@@ -230,6 +339,7 @@ class PurchaseOrder(models.Model):
'invoice_date': current_date,
'date': current_date,
'invoice_origin': self.name,
+ 'purchase_order_id': self.id,
'move_type': 'in_invoice'
}
@@ -240,7 +350,7 @@ class PurchaseOrder(models.Model):
data_line_bills = {
'move_id': bills.id,
'product_id': product_dp.id, # product down payment
- 'account_id': 401, # Uang Muka persediaan barang dagang
+ 'account_id': 669, # Uang Muka persediaan barang dagang
'quantity': 1,
'product_uom_id': 1,
'tax_ids': [line[0].taxes_id.id for line in self.order_line],
@@ -306,6 +416,7 @@ class PurchaseOrder(models.Model):
invoice_vals = {
'ref': self.partner_ref or '',
'move_type': move_type,
+ 'purchase_order_id': self.id,
'narration': self.notes,
'currency_id': self.currency_id.id,
'invoice_user_id': self.user_id and self.user_id.id or self.env.user.id,
@@ -387,6 +498,13 @@ class PurchaseOrder(models.Model):
}
return action
+ def open_form_multi_ask_approval_po(self):
+ action = self.env['ir.actions.act_window']._for_xml_id('indoteknik_custom.action_purchase_order_multi_ask_approval')
+ action['context'] = {
+ 'po_ids': [x.id for x in self]
+ }
+ return action
+
def open_form_multi_create_uang_muka(self):
action = self.env['ir.actions.act_window']._for_xml_id('indoteknik_custom.action_purchase_order_multi_uangmuka')
action['context'] = {
@@ -414,6 +532,13 @@ class PurchaseOrder(models.Model):
purchase.button_confirm()
+ def action_multi_ask_approval_po(self):
+ for purchase in self:
+ if purchase.state != 'draft':
+ continue
+
+ purchase.po_approve()
+
def open_form_multi_update_paid_status(self):
action = self.env['ir.actions.act_window']._for_xml_id('indoteknik_custom.action_purchase_order_multi_update')
action['context'] = {
@@ -653,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()
@@ -708,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
@@ -1044,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_line.py b/indoteknik_custom/models/purchase_order_line.py
index 587a09a1..033469b8 100755
--- a/indoteknik_custom/models/purchase_order_line.py
+++ b/indoteknik_custom/models/purchase_order_line.py
@@ -321,32 +321,60 @@ class PurchaseOrderLine(models.Model):
def compute_item_margin(self):
sum_so_margin = sum_sales_price = sum_margin = 0
+
for line in self:
- if not line.product_id or line.product_id.type == 'service' or not self.order_id.sale_order_id:
+ product = line.product_id
+ order = line.order_id
+
+ # Skip jika tidak ada product_id, produk adalah service, atau tidak ada purchase order terkait
+ if not product or product.type == 'service' or not order:
line.so_item_margin = 0
line.so_item_percent_margin = 0
line.item_margin = 0
line.item_percent_margin = 0
continue
- sale_order_line = self.env['sale.order.line'].search(
- [('product_id', '=', line.product_id.id),
- ('order_id', '=', line.order_id.sale_order_id.id)], limit=1, order='price_reduce_taxexcl')
- line.so_item_margin = sale_order_line.item_margin
- line.so_item_percent_margin = sale_order_line.item_percent_margin
- sum_so_margin += sale_order_line.item_margin
- sales_price = sale_order_line.price_reduce_taxexcl * sale_order_line.product_uom_qty
- if sale_order_line.order_id.shipping_cost_covered == 'indoteknik':
- sales_price -= sale_order_line.delivery_amt_line
- if sale_order_line.order_id.fee_third_party > 0:
- sales_price -= sale_order_line.fee_third_party_line
- sum_sales_price += sales_price
+
+ # Cari semua sale.order.line terkait dengan purchase.order melalui tabel purchase.order.sales.match
+ sales_matches = self.env['purchase.order.sales.match'].search([
+ ('purchase_order_id', '=', order.id),
+ ('product_id', '=', product.id)
+ ])
+
+ total_sales_price = total_margin = total_qty_so = 0
+ for match in sales_matches:
+ sale_order_line = match.sale_line_id
+
+ # Hitung harga jual setelah mempertimbangkan biaya tambahan
+ sales_price = sale_order_line.price_reduce_taxexcl * match.qty_so
+ if sale_order_line.order_id.shipping_cost_covered == 'indoteknik':
+ sales_price -= sale_order_line.delivery_amt_line
+ if sale_order_line.order_id.fee_third_party > 0:
+ sales_price -= sale_order_line.fee_third_party_line
+
+ total_sales_price += sales_price
+ total_margin += sale_order_line.item_margin
+ total_qty_so += match.qty_so
+
+ # Set margin berdasarkan total dari semua sales order yang terkait
+ line.so_item_margin = total_margin
+ line.so_item_percent_margin = (total_margin / total_sales_price) * 100 if total_sales_price else 0
+
+ sum_so_margin += total_margin
+ sum_sales_price += total_sales_price
+
+ # Hitung harga pembelian dengan mempertimbangkan biaya pengiriman
purchase_price = line.price_subtotal
- if line.order_id.delivery_amount > 0:
+ if order.delivery_amount > 0:
purchase_price += line.delivery_amt_line
- real_item_margin = sales_price - purchase_price
- real_item_percent_margin = round((real_item_margin/sales_price), 2) * 100
+
+ # Hitung margin dan persentase margin
+ real_item_margin = total_sales_price - purchase_price
+ real_item_percent_margin = (real_item_margin / total_sales_price) * 100 if total_sales_price else 0
+
+ # Set nilai margin ke dalam line
line.item_margin = real_item_margin
line.item_percent_margin = real_item_percent_margin
+
sum_margin += real_item_margin
def compute_delivery_amt_line(self):
diff --git a/indoteknik_custom/models/purchase_order_multi_ask_approval.py b/indoteknik_custom/models/purchase_order_multi_ask_approval.py
new file mode 100644
index 00000000..69f8e5a8
--- /dev/null
+++ b/indoteknik_custom/models/purchase_order_multi_ask_approval.py
@@ -0,0 +1,22 @@
+from odoo import models, fields
+import logging
+
+_logger = logging.getLogger(__name__)
+
+
+class PurchaseOrderMultiUpdate(models.TransientModel):
+ _name = 'purchase.order.multi_ask_approval'
+
+ def save_multi_ask_approval_po(self):
+ po_ids = self._context['po_ids']
+ purchase = self.env['purchase.order'].browse(po_ids)
+ purchase.action_multi_ask_approval_po()
+ return {
+ 'type': 'ir.actions.client',
+ 'tag': 'display_notification',
+ 'params': {
+ 'title': 'Notification',
+ 'message': 'PO berhasil di ask_approval',
+ 'next': {'type': 'ir.actions.act_window_close'},
+ }
+ } \ No newline at end of file
diff --git a/indoteknik_custom/models/purchase_order_multi_uangmuka.py b/indoteknik_custom/models/purchase_order_multi_uangmuka.py
index dd63e698..0570efd9 100644
--- a/indoteknik_custom/models/purchase_order_multi_uangmuka.py
+++ b/indoteknik_custom/models/purchase_order_multi_uangmuka.py
@@ -76,7 +76,7 @@ class PurchaseOrderMultiUangmuka(models.TransientModel):
param_debit = {
'move_id': account_move.id,
- 'account_id': 401, # uang muka persediaan barang dagang
+ 'account_id': 669, # uang muka persediaan barang dagang
'partner_id': partner_id,
'currency_id': 12,
'debit': order.amount_total,
diff --git a/indoteknik_custom/models/purchase_order_sales_match.py b/indoteknik_custom/models/purchase_order_sales_match.py
index d1d929d3..0bd0092b 100644
--- a/indoteknik_custom/models/purchase_order_sales_match.py
+++ b/indoteknik_custom/models/purchase_order_sales_match.py
@@ -24,6 +24,19 @@ class PurchaseOrderSalesMatch(models.Model):
margin_item = fields.Float(string='Margin')
delivery_amt = fields.Float(string='Delivery Amount', compute='_compute_delivery_amt')
margin_deduct = fields.Float(string='After Deduct', compute='_compute_delivery_amt')
+ 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:
+ line.purchase_line_id = self.env['purchase.order.line'].search([('order_id', '=', line.purchase_order_id.id), ('product_id', '=', line.product_id.id)])
+
+ def _compute_purchase_price_po(self):
+ for line in self:
+ purchase_price = self.env['purchase.order.line'].search([('order_id', '=', line.purchase_order_id.id), ('product_id', '=', line.product_id.id)])
+ line.purchase_price_po = purchase_price.price_unit
def _compute_delivery_amt(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 74b5134e..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
@@ -182,7 +192,8 @@ class OutstandingSales(models.Model):
join product_product pp on pp.id = sm.product_id
join product_template pt on pt.id = pp.product_tmpl_id
left join x_manufactures xm on xm.id = pt.x_manufacture
- where sp.state in ('draft', 'waiting', 'confirmed')
+ where sp.state in ('draft', 'waiting', 'confirmed', 'assigned')
+ and sm.state in ('draft', 'waiting', 'confirmed', 'partially_available')
and sp.name like '%OUT%'
)
""")
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/report_logbook_sj.py b/indoteknik_custom/models/report_logbook_sj.py
index a1b6299c..17119c12 100644
--- a/indoteknik_custom/models/report_logbook_sj.py
+++ b/indoteknik_custom/models/report_logbook_sj.py
@@ -29,6 +29,8 @@ class ReportLogbookSJ(models.Model):
string='Status',
tracking=True,
)
+
+ sj_number = fields.Char(string='Picking', related='report_logbook_sj_line.name')
count_line = fields.Char(string='Count Line', compute='_compute_count_line')
diff --git a/indoteknik_custom/models/requisition.py b/indoteknik_custom/models/requisition.py
index c972b485..1d350929 100644
--- a/indoteknik_custom/models/requisition.py
+++ b/indoteknik_custom/models/requisition.py
@@ -276,10 +276,11 @@ class RequisitionLine(models.Model):
_name = 'requisition.line'
_description = 'Requisition Line'
_order = 'requisition_id, id'
+ _inherit = ['mail.thread']
requisition_id = fields.Many2one('requisition', string='Ref', required=True, ondelete='cascade', index=True, copy=False)
brand_id = fields.Many2one('x_manufactures', string='Brand')
- product_id = fields.Many2one('product.product', string='Product')
+ product_id = fields.Many2one('product.product', string='Product', tracking=3,)
partner_id = fields.Many2one('res.partner', string='Vendor')
qty_purchase = fields.Float(string='Qty Purchase')
price_unit = fields.Float(string='Price')
@@ -331,6 +332,45 @@ class RequisitionLine(models.Model):
line.taxes_id = taxes
line.partner_id = purchase_pricelist.vendor_id.id
+ @api.model
+ def create(self, vals):
+ record = super(RequisitionLine, self).create(vals)
+ record._track_changes('Tambah')
+ return record
+
+ def write(self, vals):
+ for record in self:
+ old_values = {field: record[field] for field in vals if field in record}
+
+ result = super(RequisitionLine, self).write(vals)
+
+ for record in self:
+ record._track_changes('Updated', old_values)
+
+ return result
+
+ def unlink(self):
+ for record in self:
+ record._track_changes('Hapus')
+ return super(RequisitionLine, self).unlink()
+
+ def _track_changes(self, action, old_values=None):
+ message = f"Produk telah di-{action} : <br/>"
+ if action == 'Tambah':
+ # message += f"<br/> Product: {self.product_id.name}"
+ message += f"Product: {self.product_id.name} <br/> Vendor: {self.partner_id.name} <br/> Qty: {self.qty_purchase} <br/> Price: {self.price_unit} <br/> Tax: {self.tax_id.name} <br/> Subtotal: {self.subtotal} <br/> Brand: {self.brand_id.x_name}"
+ elif action == 'Hapus':
+ # message += f"<br/> Deleted Product: {self.product_id.name}"
+ message += f"<br/> Deleted Product: {self.product_id.name} <br/> Vendor: {self.partner_id.name} Qty: {self.qty_purchase} <br/> Price: {self.price_unit} <br/> Tax: {self.tax_id.name} <br/> Subtotal: {self.subtotal} <br/> Brand: {self.brand_id.x_name}"
+ else: # Updated
+ for field, old_value in old_values.items():
+ new_value = self[field]
+ if old_value != new_value:
+ field_label = self._fields[field].string # Ambil nama label field
+ message += f"{field_label}: {old_value} -> {new_value}<br/>"
+
+ if self.requisition_id:
+ self.requisition_id.message_post(body=message)
class RequisitionPurchaseMatch(models.Model):
_name = 'requisition.purchase.match'
diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py
index 7852682f..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')
@@ -263,6 +275,45 @@ class ResPartner(models.Model):
# # raise UserError('You name it')
#
return res
+
+ @api.constrains('name')
+ def _check_duplicate_name(self):
+ for record in self:
+ if record.name:
+ existing_partner = self.env['res.partner'].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
@@ -321,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)
@@ -328,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)
@@ -336,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)
@@ -444,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'),
@@ -451,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'),
@@ -459,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'),
@@ -625,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()
@@ -639,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/res_users.py b/indoteknik_custom/models/res_users.py
index 31b84ae3..b0864f2c 100755
--- a/indoteknik_custom/models/res_users.py
+++ b/indoteknik_custom/models/res_users.py
@@ -70,6 +70,10 @@ class ResUsers(models.Model):
vouchers = self.env['voucher'].get_active_voucher([('show_on_email', '=', 'user_activation')])
if not vouchers: return None
return ', '.join(x.code for x in vouchers)
+ if type == 'switch_account':
+ vouchers = self.env['voucher'].get_active_voucher([('excl_pricelist_ids', 'not in', [1]), ('apply_type', 'in', ['all', 'brand']), ('account_type', 'in', ['all', 'company']), ('visibility', 'in', ['public'])])
+ if not vouchers: return None
+ return ', '.join(x.code for x in vouchers)
return None
def check_access(self, model, mode):
diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py
index 48195b77..0d4fc6c3 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
@@ -7,16 +9,133 @@ from collections import defaultdict
_logger = logging.getLogger(__name__)
+class CancelReasonOrder(models.TransientModel):
+ _name = 'cancel.reason.order'
+ _description = 'Wizard for Cancel Reason order'
+
+ request_id = fields.Many2one('sale.order', string='Request')
+ reason_cancel = fields.Selection([
+ ('harga_terlalu_mahal', 'Harga barang terlalu mahal'),
+ ('harga_web_tidak_valid', 'Harga web tidak valid'),
+ ('stok_kosong', 'Stock kosong'),
+ ('tidak_mau_indent', 'Customer tidak mau indent'),
+ ('batal_rencana_pembelian', 'Customer membatalkan rencana pembelian'),
+ ('vendor_tidak_support_demo', 'Vendor tidak support demo/trial product'),
+ ('product_knowledge_kurang', 'Product knowledge kurang baik'),
+ ('barang_tidak_sesuai', 'Barang tidak sesuai/tepat'),
+ ('tidak_sepakat_pembayaran', 'Tidak menemukan kesepakatan untuk pembayaran'),
+ ('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', required=True, copy=False, index=True, tracking=3)
+ attachment_bukti = fields.Many2many(
+ 'ir.attachment',
+ string="Attachment Bukti", readonly=False,
+ tracking=3, required=True
+ )
+ nomor_so_pengganti = fields.Char(string='Nomor SO Pengganti', copy=False, tracking=3)
+
+ def confirm_reject(self):
+ order = self.request_id
+ if order:
+ order.write({'reason_cancel': self.reason_cancel})
+ if not self.attachment_bukti:
+ raise UserError('Attachment bukti wajib disertakan')
+ order.write({'attachment_bukti': self.attachment_bukti})
+ order.message_post(body='Attachment Bukti Cancel',
+ attachment_ids=[self.attachment_bukti.id])
+ if self.reason_cancel == 'ganti_quotation':
+ if self.nomor_so_pengganti:
+ order.write({'nomor_so_pengganti': self.nomor_so_pengganti})
+ else:
+ raise UserError('Nomor SO pengganti wajib disertakan')
+ 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'),
@@ -28,11 +147,11 @@ class SaleOrder(models.Model):
shipping_cost_covered = fields.Selection([
('indoteknik', 'Indoteknik'),
('customer', 'Customer')
- ], string='Shipping Covered by', help='Siapa yang menanggung biaya ekspedisi?', copy=False)
+ ], string='Shipping Covered by', help='Siapa yang menanggung biaya ekspedisi?', copy=False, tracking=3)
shipping_paid_by = fields.Selection([
('indoteknik', 'Indoteknik'),
('customer', 'Customer')
- ], string='Shipping Paid by', help='Siapa yang talangin dulu Biaya ekspedisi-nya?', copy=False)
+ ], string='Shipping Paid by', help='Siapa yang talangin dulu Biaya ekspedisi-nya?', copy=False, tracking=3)
sales_tax_id = fields.Many2one('account.tax', string='Tax', domain=['|', ('active', '=', False), ('active', '=', True)])
have_outstanding_invoice = fields.Boolean('Have Outstanding Invoice', compute='_have_outstanding_invoice')
have_outstanding_picking = fields.Boolean('Have Outstanding Picking', compute='_have_outstanding_picking')
@@ -48,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'),
@@ -83,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)
@@ -93,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([
@@ -144,6 +266,121 @@ 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'),
+ ('stok_kosong', 'Stock kosong'),
+ ('tidak_mau_indent', 'Customer tidak mau indent'),
+ ('batal_rencana_pembelian', 'Customer membatalkan rencana pembelian'),
+ ('vendor_tidak_support_demo', 'Vendor tidak support demo/trial product'),
+ ('product_knowledge_kurang', 'Product knowledge kurang baik'),
+ ('barang_tidak_sesuai', 'Barang tidak sesuai/tepat'),
+ ('tidak_sepakat_pembayaran', 'Tidak menemukan kesepakatan untuk pembayaran'),
+ ('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)
+
+ 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:
+ if order.picking_ids:
+ carrier_names = order.picking_ids.mapped('carrier_id.name')
+ order.shipping_method_picking = ', '.join(filter(None, carrier_names))
+ else:
+ order.shipping_method_picking = False
@api.onchange('payment_status')
def _is_continue_transaction(self):
@@ -183,15 +420,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 = []
@@ -209,35 +467,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'
@@ -337,11 +610,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
@@ -363,19 +636,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):
"""
@@ -426,6 +811,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')
@@ -540,22 +952,22 @@ class SaleOrder(models.Model):
redirect_url = json.loads(lookup_json)['redirect_url']
self.payment_link_midtrans = str(redirect_url)
- # Generate QR code
- qr = qrcode.QRCode(
- version=1,
- error_correction=qrcode.constants.ERROR_CORRECT_L,
- box_size=10,
- border=4,
- )
- qr.add_data(redirect_url)
- qr.make(fit=True)
- img = qr.make_image(fill_color="black", back_color="white")
+ if 'redirect_url' in response:
+ qr = qrcode.QRCode(
+ version=1,
+ error_correction=qrcode.constants.ERROR_CORRECT_L,
+ box_size=10,
+ border=4,
+ )
+ qr.add_data(redirect_url)
+ qr.make(fit=True)
+ img = qr.make_image(fill_color="black", back_color="white")
- buffer = BytesIO()
- img.save(buffer, format="PNG")
- qr_code_img = base64.b64encode(buffer.getvalue()).decode()
+ buffer = BytesIO()
+ img.save(buffer, format="PNG")
+ qr_code_img = base64.b64encode(buffer.getvalue()).decode()
- self.payment_qr_code = qr_code_img
+ self.payment_qr_code = qr_code_img
@api.model
def _generate_so_access_token(self, limit=50):
@@ -570,17 +982,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([
@@ -634,6 +1043,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):
@@ -735,6 +1151,7 @@ class SaleOrder(models.Model):
self._validate_order()
for order in self:
+ order._validate_uniform_taxes()
order.order_line.validate_line()
term_days = 0
@@ -756,15 +1173,38 @@ 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:
return self._create_notification_action('Notification', 'Terdapat Vendor yang berbeda dengan MD Vendor')
self.check_due()
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()
@@ -816,7 +1256,9 @@ 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'
return self._create_approval_notification('Sales Manager')
@@ -923,6 +1365,29 @@ class SaleOrder(models.Model):
raise UserError(_("%s is in Blocking Stage, Remaining credit limit is %s, from %s and outstanding %s")
% (rec.partner_id.name, remaining_credit_limit, block_stage, outstanding_amount))
+ def check_limit_so_to_invoice(self):
+ for rec in self:
+ # Ambil jumlah outstanding_amount dan rec.amount_total sebagai current_total
+ outstanding_amount = rec.outstanding_amount
+ current_total = rec.amount_total + outstanding_amount
+
+ # Ambil blocking stage dari partner
+ block_stage = rec.partner_id.parent_id.blocking_stage if rec.partner_id.parent_id else rec.partner_id.blocking_stage or 0
+ is_cbd = rec.partner_id.parent_id.property_payment_term_id.id == 26 if rec.partner_id.parent_id else rec.partner_id.property_payment_term_id.id == 26 or False
+
+ # Ambil jumlah nilai dari SO yang invoice_status masih 'to invoice'
+ so_to_invoice = 0
+ for sale in rec.partner_id.sale_order_ids:
+ if sale.invoice_status == 'to invoice':
+ so_to_invoice = so_to_invoice + sale.amount_total
+ # Hitung remaining credit limit
+ remaining_credit_limit = block_stage - current_total - so_to_invoice
+
+ # Validasi limit
+ if remaining_credit_limit <= 0 and block_stage > 0 and not is_cbd:
+ raise UserError(_("The credit limit for %s will exceed the Blocking Stage if the Sale Order is confirmed. The remaining credit limit is %s, from %s and the outstanding amount is %s.")
+ % (rec.partner_id.name, block_stage - current_total, block_stage, outstanding_amount))
+
def validate_different_vendor(self):
if self.vendor_approval_id.filtered(lambda v: v.state == 'draft'):
draft_names = ", ".join(self.vendor_approval_id.filtered(lambda v: v.state == 'draft').mapped('number'))
@@ -971,6 +1436,11 @@ 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:
return self._create_notification_action('Notification', 'Terdapat Vendor yang berbeda dengan MD Vendor')
@@ -1009,6 +1479,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()
@@ -1026,15 +1497,22 @@ 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()
if self.have_outstanding_invoice:
raise UserError("Invoice harus di Cancel dahulu")
+
+ disallow_states = ['draft', 'waiting', 'confirmed', 'assigned']
+ for picking in self.picking_ids:
+ if picking.state in disallow_states:
+ raise UserError("DO yang draft, waiting, confirmed, atau assigned harus di-cancel oleh Logistik")
for line in self.order_line:
if line.qty_delivered > 0:
- raise UserError("DO harus di-cancel terlebih dahulu.")
+ raise UserError("DO yang done harus di-Return oleh Logistik")
if not self.web_approval:
self.web_approval = 'company'
@@ -1045,8 +1523,24 @@ class SaleOrder(models.Model):
self.due_id = False
if main_parent.use_so_approval:
self.send_notif_to_salesperson(cancel=True)
+ for order in self:
+ if order.amount_total > 30000000:
+ return {
+ 'type': 'ir.actions.act_window',
+ 'name': _('Cancel Reason'),
+ 'res_model': 'cancel.reason.order',
+ 'view_mode': 'form',
+ 'target': 'new',
+ 'context': {'default_request_id': self.id},
+ }
return super(SaleOrder, self).action_cancel()
-
+
+ def confirm_cancel_order(self):
+ """Fungsi ini akan dipanggil oleh wizard setelah alasan pembatalan dipilih"""
+ if self.state != 'cancel':
+ self.state = 'cancel'
+ return super(SaleOrder, self).action_cancel()
+
def validate_partner_invoice_due(self):
parent_id = self.partner_id.parent_id.id
parent_id = parent_id if parent_id else self.partner_id.id
@@ -1078,10 +1572,11 @@ class SaleOrder(models.Model):
return False
def _requires_approval_margin_leader(self):
- return self.total_percent_margin <= 13 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 <= 20 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'
@@ -1118,8 +1613,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:
@@ -1129,7 +1632,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')
@@ -1383,18 +1888,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:
@@ -1423,5 +1932,11 @@ 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)
+ # 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 \ 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/sale_order_multi_uangmuka_penjualan.py b/indoteknik_custom/models/sale_order_multi_uangmuka_penjualan.py
index f9120290..96c2f676 100644
--- a/indoteknik_custom/models/sale_order_multi_uangmuka_penjualan.py
+++ b/indoteknik_custom/models/sale_order_multi_uangmuka_penjualan.py
@@ -68,7 +68,7 @@ class PurchaseOrderMultiUangmukaPenjualan(models.TransientModel):
param_credit = {
'move_id': account_move.id,
- 'account_id': 449, # uang muka penjualan
+ 'account_id': 668, # penerimaan belum alokasi
'partner_id': partner_id,
'currency_id': 12,
'debit': 0,
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 87e8370f..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')
@@ -76,12 +76,21 @@ class ProductTemplate(models.Model):
('product_id', 'in', template.product_variant_ids.ids),
('location_id', 'in', target_locations),
])
-
- is_in_bu = any(quant.available_quantity > 0 for quant in stock_quant)
+ is_in_bu = False
+ for quant in stock_quant:
+ if quant.product_id.qty_free_bandengan > 0:
+ is_in_bu = True
+ break
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,
@@ -91,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 4be0dff2..c2a293f9 100644
--- a/indoteknik_custom/models/stock_immediate_transfer.py
+++ b/indoteknik_custom/models/stock_immediate_transfer.py
@@ -5,18 +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_mail_bills()
+ # picking.send_koli_to_so()
# If still in draft => confirm and assign
if picking.state == 'draft':
picking.action_confirm()
@@ -24,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
@@ -33,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
new file mode 100644
index 00000000..69cca5bc
--- /dev/null
+++ b/indoteknik_custom/models/stock_inventory.py
@@ -0,0 +1,79 @@
+from odoo import models, api, fields
+from odoo.exceptions import UserError
+from datetime import datetime
+import logging
+
+_logger = logging.getLogger(__name__)
+
+
+class StockInventory(models.Model):
+ _inherit = ['stock.inventory']
+ _order = 'id desc'
+ _rec_name = 'number'
+
+ number = fields.Char(string='Document No', index=True, copy=False, readonly=True)
+ adjusment_type = fields.Selection([
+ ('in', 'Adjusment In'),
+ ('out', 'Adjusment Out'),
+ ], string='Adjusments Type', required=True)
+
+ def _generate_number_stock_inventory(self):
+ """Men-generate nomor untuk semua stock inventory yang belum memiliki number."""
+ stock_records = self.env['stock.inventory'].search([('number', '=', False)], order='id asc')
+ for record in stock_records:
+ self._assign_number(record)
+
+ _logger.info('Generate Number Done')
+
+ def _assign_number(self, record):
+ """Menentukan nomor berdasarkan kategori Adjust-In atau Adjust-Out."""
+ name_upper = record.name.upper() if record.name else ""
+
+ 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':
+ last_number = self._get_last_sequence("ADJUST/IN/")
+ record.number = f"ADJUST/IN/{last_number}"
+ else:
+ record.number = "UNKNOWN" # Jika tidak termasuk kategori
+
+ def _get_last_sequence(self, prefix):
+ """Mengambil nomor terakhir berdasarkan prefix (ADJUST/OUT/ atau ADJUST/IN/) dengan format 00001, 00002, dst."""
+ last_record = self.env['stock.inventory'].search(
+ [("number", "like", f"{prefix}%")], order="number desc", limit=1
+ )
+
+ if last_record and last_record.number:
+ try:
+ last_number = int(last_record.number.split("/")[-1]) # Ambil angka terakhir
+ return str(last_number + 1).zfill(5) # Format jadi 00001, 00002, dst.
+ except ValueError:
+ return "00001" # Jika format tidak valid, mulai dari 00001
+ return "00001" # Jika belum ada data, mulai dari 00001
+
+ @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)
+
+ 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 e1d4e74c..90ab30a4 100644
--- a/indoteknik_custom/models/stock_move.py
+++ b/indoteknik_custom/models/stock_move.py
@@ -12,9 +12,44 @@ class StockMove(models.Model):
default=lambda self: self.product_id.print_barcode,
)
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:
+ if rec.picking_id.picking_type_code == 'outgoing' and rec.picking_id and rec.picking_id.origin and rec.picking_id.origin.startswith('SO/'):
+ rec.qr_code_variant = rec.product_id.qr_code_variant
+ rec.print_barcode = True
if rec.print_barcode and rec.print_barcode == True and rec.product_id and rec.product_id.qr_code_variant:
rec.qr_code_variant = rec.product_id.qr_code_variant
else:
diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py
index cd330aeb..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,28 @@ 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')
efaktur_id = fields.Many2one('vit.efaktur', string='Faktur Pajak')
@@ -61,39 +80,50 @@ 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')
+ date_reserved = fields.Datetime(string="Date Reserved", help='Tanggal ter-reserved semua barang nya', copy=False)
status_printed = fields.Selection([
('not_printed', 'Belum Print'),
('printed', 'Printed')
@@ -111,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', readonly=True, tracking=True, help="The current state of the stock picking.")
- notee = fields.Text(string="Note")
+ ], string='Status Reserve', tracking=True, copy=False, help="The current state of the stock picking.")
+ 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):
@@ -132,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)
@@ -165,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:
@@ -172,12 +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)
- ])
+ ('carrier_id', '=', 9),
+ ('lalamove_order_id', '!=', False)
+ ])
for picking in pickings:
try:
order_id = picking.lalamove_order_id
@@ -212,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")
@@ -233,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)
@@ -330,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)])
@@ -357,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,
@@ -379,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,
@@ -387,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 == 201:
- return response.json()
+ if response.status_code == 200:
+ data = response.json()
+
+ self.biteship_id = data.get("id", "")
+ self.biteship_tracking_id = data.get("courier", {}).get("tracking_id", "")
+ self.biteship_waybill_id = data.get("courier", {}).get("waybill_id", "")
+ self.delivery_tracking_no = data.get("courier", {}).get("waybill_id", "")
+
+ waybill_id = data.get("courier", {}).get("waybill_id", "")
+
+ 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')
@@ -439,58 +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')
+ ('picking_type_code', '=', 'internal'),
+ ('name', 'ilike', 'BU/PICK/'),
])
-
+
for picking in pickings:
- fullfillments = self.env['sales.order.fullfillment'].search([
- ('sales_order_id', '=', picking.sale_id.id)
+ 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.reserved_from not in ['Inventory On Hand', 'Reserved from stock', 'Free Stock'] for rec in fullfillments):
+
+ 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/PICK/'),
+ ('picking_type_code', '=', 'internal'),
+ ('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):
@@ -500,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):
@@ -508,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)
])
@@ -528,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()
@@ -575,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)
@@ -638,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:
@@ -659,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
@@ -685,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:
@@ -708,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
@@ -733,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):
@@ -752,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")
@@ -779,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")
@@ -805,12 +1268,147 @@ 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:
+ def check_qty_per_inventory(self, product, location):
+ quant = self.env['stock.quant'].search([
+ ('product_id', '=', product.id),
+ ('location_id', '=', location.id),
+ ])
+
+ if quant:
+ 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 send_mail_bills(self):
if self.picking_type_code == 'incoming' and self.purchase_id:
@@ -864,21 +1462,75 @@ 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)
- 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
+ vals.get('location_dest_id', picking.location_dest_id.id) == 58):
+ if 'name' in vals or picking.name.startswith('BU/IN/'):
+ name_to_modify = vals.get('name', picking.name)
+ if name_to_modify.startswith('BU/IN/'):
+ vals['name'] = name_to_modify.replace('BU/IN/', 'BU/INPUT/', 1)
+
+ if (vals.get('picking_type_code', picking.picking_type_code) == 'internal' and
+ vals.get('location_id', picking.location_id.id) == 58):
+ name_to_modify = vals.get('name', picking.name)
+ 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:
+ new_name = f"{new_name}-DUP"
+ vals['name'] = new_name
return super(StockPicking, self).write(vals)
def _use_faktur(self, vals):
@@ -920,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',
@@ -945,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:
@@ -957,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': ''
},
@@ -969,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
@@ -996,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)
@@ -1005,51 +1745,510 @@ 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'
-# _order = 'picking_id, id'
-
-# picking_id = fields.Many2one('stock.picking', string='Picking Reference', required=True, ondelete='cascade', index=True, copy=False)
-# product_id = fields.Many2one('product.product', string='Product')
-
-
-# @api.constrains('product_id')
-# def check_product_validity(self):
-# """
-# Validate if the product exists in the related stock.picking's move_ids_without_package
-# and ensure that the product's quantity does not exceed the available product_uom_qty.
-# """
-# for record in self:
-# if not record.picking_id or not record.product_id:
-# continue
-
-# # Filter move lines in the related picking for the selected product
-# moves = record.picking_id.move_ids_without_package.filtered(
-# lambda move: move.product_id.id == record.product_id.id
-# )
-
-# if not moves:
-# raise UserError((
-# "The product '%s' is not available in the related stock picking's moves. "
-# "Please check and try again."
-# ) % record.product_id.display_name)
-
-# # Calculate the total entries for the product in check.product for the same picking
-# product_entries_count = self.search_count([
-# ('picking_id', '=', record.picking_id.id),
-# ('product_id', '=', record.product_id.id)
-# ])
-
-# # Sum the product_uom_qty for all relevant moves
-# total_qty_in_moves = sum(moves.mapped('product_uom_qty'))
-
-# # Compare the count of entries against the available quantity
-# if product_entries_count > total_qty_in_moves:
-# raise UserError((
-# "The product '%s' exceeds the allowable quantity (%s) in the related stock picking's moves. "
-# "You can only add it %s times."
-# ) % (record.product_id.display_name, total_qty_in_moves, total_qty_in_moves))
+
+
+class CheckProduct(models.Model):
+ _name = 'check.product'
+ _description = 'Check Product'
+ _order = 'picking_id, id'
+
+ picking_id = fields.Many2one(
+ 'stock.picking',
+ string='Picking 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.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):
+ for record in self:
+ moves = record.picking_id.move_ids_without_package.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(CheckProduct, 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(CheckProduct, 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, picking):
+ """
+ Sinkronisasi quantity_done di move_ids_without_package
+ dengan total quantity dari check.product berdasarkan product_id.
+ """
+ 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)
+ )
+ # Update quantity_done di move yang relevan
+ moves = picking.move_ids_without_package.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 picking_id
+ and sync the total quantity to related moves.
+ """
+ for picking in self.mapped('picking_id'):
+ lines_to_remove = self.env['check.product'] # Recordset untuk menyimpan baris yang akan dihapus
+ product_lines = picking.check_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.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(picking)
+
+ @api.onchange('product_id', 'quantity')
+ def check_product_validity(self):
+ for record in self:
+ if not record.picking_id or not record.product_id:
+ continue
+
+ # Filter moves related to the selected product
+ moves = record.picking_id.move_ids_without_package.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.picking_id.check_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 BarcodeProduct(models.Model):
+ _name = 'barcode.product'
+ _description = 'Barcode Product'
+ _order = 'picking_id, id'
+
+ picking_id = fields.Many2one(
+ 'stock.picking',
+ string='Picking Reference',
+ required=True,
+ ondelete='cascade',
+ index=True,
+ copy=False,
+ )
+ 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')
+
+
+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/uangmuka_pembelian.py b/indoteknik_custom/models/uangmuka_pembelian.py
index 204855d3..ba41f814 100644
--- a/indoteknik_custom/models/uangmuka_pembelian.py
+++ b/indoteknik_custom/models/uangmuka_pembelian.py
@@ -64,7 +64,7 @@ class UangmukaPembelian(models.TransientModel):
param_debit = {
'move_id': account_move.id,
- 'account_id': 401, # uang muka persediaan barang dagang
+ 'account_id': 669, # uang muka persediaan barang dagang
'partner_id': partner_id,
'currency_id': 12,
'debit': self.pay_amt,
diff --git a/indoteknik_custom/models/uangmuka_penjualan.py b/indoteknik_custom/models/uangmuka_penjualan.py
index 5acf604d..a3e95ecd 100644
--- a/indoteknik_custom/models/uangmuka_penjualan.py
+++ b/indoteknik_custom/models/uangmuka_penjualan.py
@@ -74,7 +74,7 @@ class UangmukaPenjualan(models.TransientModel):
# sisa di credit untuk uang muka penjualan, diluar ongkir dan selisih
param_credit = {
'move_id': account_move.id,
- 'account_id': 449, # uang muka penjualan
+ 'account_id': 668, # penerimaan belum alokasi
'partner_id': partner_id,
'currency_id': 12,
'debit': 0,
diff --git a/indoteknik_custom/models/user_company_request.py b/indoteknik_custom/models/user_company_request.py
index af8a86ba..9216e8eb 100644
--- a/indoteknik_custom/models/user_company_request.py
+++ b/indoteknik_custom/models/user_company_request.py
@@ -104,9 +104,9 @@ class UserCompanyRequest(models.Model):
self.user_company_id.active = True
user.send_company_switch_approve_mail() if vals.get('is_switch_account') == True else user.send_company_request_approve_mail()
else:
- new_company = self.env['res.partner'].create({
- 'name': self.user_input
- })
+ # new_company = self.env['res.partner'].create({
+ # 'name': self.user_input
+ # })
# self.user_id.parent_id = new_company.id
user.send_company_request_reject_mail()
return super(UserCompanyRequest, self).write(vals)
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 b43f56ac..565b0315 100644
--- a/indoteknik_custom/models/user_pengajuan_tempo_request.py
+++ b/indoteknik_custom/models/user_pengajuan_tempo_request.py
@@ -7,7 +7,7 @@ class RejectReasonWizard(models.TransientModel):
_description = 'Wizard for Reject Reason'
request_id = fields.Many2one('user.pengajuan.tempo.request', string='Request')
- reason_reject = fields.Text(string='Reason for Rejection', required=True)
+ reason_reject = fields.Text(string='Reason for Rejection', required=True, tracking=True)
def confirm_reject(self):
tempo = self.request_id
@@ -55,7 +55,27 @@ class UserPengajuanTempoRequest(models.Model):
('approval_director', 'Approved by Director'),
('reject', 'Rejected'),
], string='Status', readonly=True, copy=False, index=True, track_visibility='onchange', default='draft')
- reason_reject = fields.Char(string='Limit Tempo')
+ last_state_tempo = fields.Selection([
+ ('draft', 'Pengajuan Tempo'),
+ ('approval_sales', 'Approved by Sales Manager'),
+ ('approval_finance', 'Approved by Finance'),
+ ('approval_director', 'Approved by Director'),
+ ('reject', 'Rejected'),
+ ], string='Status',)
+ reason_reject = fields.Char(string='Reason Reaject', tracking=True, track_visibility='onchange')
+ state_tempo_text = fields.Char(string="Status", compute="_compute_state_tempo_text")
+
+ @api.depends('state_tempo')
+ def _compute_state_tempo_text(self):
+ for record in self:
+ status_mapping = {
+ 'draft': "Menunggu Approve Manager",
+ 'approval_sales': "Menunggu Approve Finance",
+ 'approval_finance': "Menunggu Approve Direktur",
+ 'approval_director': "Approved",
+ 'reject': "Rejected",
+ }
+ record.state_tempo_text = status_mapping.get(record.state_tempo, "Unknown")
# informasi perusahaan
name_tempo = fields.Many2one('res.partner', string='Nama Perusahaan', related='pengajuan_tempo_id.name_tempo', store=True, tracking=True, readonly=False)
@@ -71,7 +91,7 @@ class UserPengajuanTempoRequest(models.Model):
website_tempo = fields.Char(string='Website', related='pengajuan_tempo_id.website_tempo', store=True, tracking=True, readonly=False)
portal = fields.Boolean(string='Portal Website', related='pengajuan_tempo_id.portal', store=True, tracking=True, readonly=False)
estimasi_tempo = fields.Char(string='Estimasi Pembelian Pertahun', related='pengajuan_tempo_id.estimasi_tempo', store=True, tracking=True, readonly=False)
- tempo_duration_origin = fields.Many2one('account.payment.term', string='Durasi Tempo', related='pengajuan_tempo_id.tempo_duration', store=True, tracking=True, readonly=False, domain=[('id', 'in', [24, 25, 29, 32])])
+ tempo_duration_origin = fields.Many2one('account.payment.term', string='Durasi Tempo', related='tempo_duration', store=True, tracking=True, readonly=False, domain=[('id', 'in', [24, 25, 29, 32])])
tempo_limit_origin = fields.Char(string='Limit Tempo', related='pengajuan_tempo_id.tempo_limit' , store=True, tracking=True, readonly=False)
category_produk_ids = fields.Many2many('product.public.category', string='Kategori Produk yang Digunakan', related='pengajuan_tempo_id.category_produk_ids', readonly=False)
@@ -88,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)
@@ -110,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)
@@ -272,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
@@ -290,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
@@ -304,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):
@@ -317,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
@@ -334,13 +365,13 @@ class UserPengajuanTempoRequest(models.Model):
@api.onchange('tempo_duration')
def _tempo_duration_change(self):
for tempo in self:
- if tempo.env.user.id not in (7, 377, 12182):
+ if tempo.env.user.id not in (7, 688, 28, 377, 12182):
raise UserError("Durasi tempo hanya bisa di ubah oleh Sales Manager atau Direktur")
@api.onchange('tempo_limit')
def _onchange_tempo_limit(self):
for tempo in self:
- if tempo.env.user.id not in (7, 377, 12182):
+ if tempo.env.user.id not in (7, 688, 28, 377, 12182):
raise UserError("Limit tempo hanya bisa diubah oleh Sales Manager atau Direktur")
def button_approve(self):
for tempo in self:
@@ -409,6 +440,9 @@ class UserPengajuanTempoRequest(models.Model):
'target': 'new',
'context': {'default_request_id': self.id},
}
+ def button_draft(self):
+ for tempo in self:
+ tempo.state_tempo = tempo.last_state_tempo if tempo.last_state_tempo else 'draft'
def write(self, vals):
is_approve = True if self.state_tempo == 'approval_director' or vals.get('state_tempo') == 'approval_director' else False
@@ -486,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,
@@ -496,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,
@@ -507,10 +543,25 @@ class UserPengajuanTempoRequest(models.Model):
# Buat kontak baru untuk company_id
for contact_data in contacts_data:
- self.env['res.partner'].create({
- "parent_id": self.user_company_id.id, # Hubungkan ke perusahaan
- **contact_data, # Tambahkan data kontak
- })
+ existing_contact = self.env['res.partner'].search([
+ ('parent_id', '=', self.user_company_id.id),
+ ('name', '=', contact_data['name'])
+ ], limit=1)
+
+ 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'])
+ ], limit=1)
+
+ if duplicate_check:
+ # Jika nama sudah ada tetapi di perusahaan lain, tambahkan nama perusahaan
+ contact_data['name'] = f"{contact_data['name']} ({self.user_company_id.name})"
+
+ self.env['res.partner'].create({
+ "parent_id": self.user_company_id.id, # Hubungkan ke perusahaan
+ **contact_data, # Tambahkan data kontak
+ })
# Pengiriman
self.user_company_id.pic_name = self.pengajuan_tempo_id.pic_name
@@ -532,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
@@ -541,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])
@@ -596,10 +653,10 @@ class UserPengajuanTempoRequest(models.Model):
attachment_ids=[self.user_company_id.dokumen_tempat_bekerja.id])
# self.user_company_id.active = True
# user.send_company_request_approve_mail()
- self.user_company_id.property_payment_term_id = self.pengajuan_tempo_id.tempo_duration.id
+ self.user_company_id.property_payment_term_id = self.tempo_duration.id
self.user_company_id.active_limit = True
- self.user_company_id.warning_stage = float(limit_tempo) - (float(limit_tempo)/2)
self.user_company_id.blocking_stage = limit_tempo
+ self.user_company_id.warning_stage = float(limit_tempo) - (float(limit_tempo)/2)
# Internal Notes
comment = []
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 37c97338..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=[
@@ -59,13 +63,45 @@ class Voucher(models.Model):
show_on_email = fields.Selection([
('user_activation', 'User Activation')
], 'Show on Email')
+ account_type = fields.Selection(string='Account Type', default="all", selection=[
+ ('all', "All Account"),
+ ('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:
@@ -83,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 = {
@@ -96,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)
@@ -112,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:
@@ -128,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:
@@ -169,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 036e28fc..28fc760d 100755
--- a/indoteknik_custom/security/ir.model.access.csv
+++ b/indoteknik_custom/security/ir.model.access.csv
@@ -105,6 +105,7 @@ access_purchase_order_multi_update,access.purchase.order.multi_update,model_purc
access_invoice_reklas_penjualan,access.invoice.reklas.penjualan,model_invoice_reklas_penjualan,,1,1,1,1
access_invoice_reklas_penjualan_line,access.invoice.reklas.penjualan.line,model_invoice_reklas_penjualan_line,,1,1,1,1
access_purchase_order_multi_confirm,access.purchase.order.multi_confirm,model_purchase_order_multi_confirm,,1,1,1,1
+access_purchase_order_multi_ask_approval,access.purchase.order.multi_ask_approval,model_purchase_order_multi_ask_approval,,1,1,1,1
access_po_multi_cancel,access.po.multi.cancel,model_po_multi_cancel,,1,1,1,1
access_logbook_sj,access.logbook.sj,model_logbook_sj,,1,1,1,1
access_logbook_sj_line,access.logbook.sj.line,model_logbook_sj_line,,1,1,1,1
@@ -137,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
@@ -150,9 +152,18 @@ 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
@@ -162,3 +173,15 @@ access_user_form_merchant,access.user.form.merchant,model_user_form_merchant,,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 36b292e8..46737a40 100644
--- a/indoteknik_custom/views/account_move.xml
+++ b/indoteknik_custom/views/account_move.xml
@@ -27,6 +27,7 @@
</field>
<field name="invoice_date" position="after">
<field name="sale_id" readonly="1" attrs="{'invisible': [('move_type', '!=', 'out_invoice')]}"/>
+ <field name="purchase_order_id" readonly="1" attrs="{'invisible': [('move_type', '!=', 'in_invoice')]}"/>
</field>
<field name="ref" position="after">
<field name="sale_id" readonly="1" attrs="{'invisible': [('move_type', '!=', 'entry')]}"/>
@@ -91,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'" />
@@ -137,6 +139,9 @@
{'column_invisible': [('parent.move_type', '!=', 'in_invoice')]}
</attribute>
</field>
+ <field name="product_id" position="before">
+ <field name="line_no" optional="hide"/>
+ </field>
<field name="invoice_incoterm_id" position="after">
<field name="date_terima_tukar_faktur"/>
</field>
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
new file mode 100644
index 00000000..55876580
--- /dev/null
+++ b/indoteknik_custom/views/barcoding_product.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<odoo>
+ <data>
+ <record id="barcoding_product_tree" model="ir.ui.view">
+ <field name="name">barcoding.product.tree</field>
+ <field name="model">barcoding.product</field>
+ <field name="arch" type="xml">
+ <tree default_order="create_date desc">
+ <field name="product_id"/>
+ <field name="quantity"/>
+ <field name="type"/>
+ </tree>
+ </field>
+ </record>
+
+ <record id="barcoding_product_line_tree" model="ir.ui.view">
+ <field name="name">barcoding.product.line.tree</field>
+ <field name="model">barcoding.product.line</field>
+ <field name="arch" type="xml">
+ <tree>
+ <field name="product_id"/>
+ <field name="qr_code_variant" widget="image"/>
+ </tree>
+ </field>
+ </record>
+
+ <record id="barcoding_product_form" model="ir.ui.view">
+ <field name="name">barcoding.product.form</field>
+ <field name="model">barcoding.product</field>
+ <field name="arch" type="xml">
+ <form >
+ <sheet>
+ <group>
+ <group>
+ <field name="product_id" required="1"/>
+ <field name="type" required="1"/>
+ <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','barcoding_box')]]}">
+ <field name="barcoding_product_line"/>
+ </page>
+ </notebook>
+ </sheet>
+ </form>
+ </field>
+ </record>
+
+ <record id="barcoding_product_view_search" model="ir.ui.view">
+ <field name="name">barcoding.product.search.view</field>
+ <field name="model">barcoding.product</field>
+ <field name="arch" type="xml">
+ <search string="Search Barcoding Product">
+ <field name="product_id"/>
+ </search>
+ </field>
+ </record>
+
+ <record id="barcoding_product_action" model="ir.actions.act_window">
+ <field name="name">Barcoding Product</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">barcoding.product</field>
+ <field name="view_mode">tree,form</field>
+ </record>
+
+ <menuitem
+ id="menu_barcoding_product"
+ name="Barcoding Product"
+ parent="stock.menu_stock_warehouse_mgmt"
+ sequence="4"
+ action="barcoding_product_action"
+ />
+ </data>
+</odoo>
diff --git a/indoteknik_custom/views/customer_commision.xml b/indoteknik_custom/views/customer_commision.xml
index 0b72587e..37df16ff 100644
--- a/indoteknik_custom/views/customer_commision.xml
+++ b/indoteknik_custom/views/customer_commision.xml
@@ -11,8 +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"/>
<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>
@@ -24,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>
@@ -39,60 +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')]}"/>
+ <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="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>
@@ -100,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>
@@ -113,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">
@@ -157,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>
@@ -197,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 022937f4..b58139c6 100755
--- a/indoteknik_custom/views/purchase_order.xml
+++ b/indoteknik_custom/views/purchase_order.xml
@@ -51,6 +51,9 @@
<field name="approval_status" position="after">
<field name="approval_status_unlock" invisible="True"/>
</field>
+ <field name="partner_id" position="after">
+ <field name="purchase_order_count"/>
+ </field>
<field name="incoterm_id" position="after">
<field name="amount_total_without_service"/>
<field name="delivery_amt"/>
@@ -62,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"/>
@@ -78,6 +82,9 @@
<field name="product_id" position="attributes">
<attribute name="options">{'no_create': True}</attribute>
</field>
+ <field name="date_planned" position="attributes">
+ <attribute name="invisible">1</attribute>
+ </field>
<field name="product_qty" position="before">
<field name="is_edit_product_qty" readonly="1" optional="hide"/>
<field name="qty_onhand" readonly="1" optional="hide"/>
@@ -103,6 +110,10 @@
<field name="price_vendor" attrs="{'readonly': 1}" optional="hide"/>
</field>
<field name="price_subtotal" position="after">
+ <field name="so_item_margin" attrs="{'readonly': 1}" optional="hide"/>
+ <field name="so_item_percent_margin" attrs="{'readonly': 1}" optional="hide"/>
+ <field name="item_margin" attrs="{'readonly': 1}" optional="hide"/>
+ <field name="item_percent_margin" attrs="{'readonly': 1}" optional="hide"/>
<field name="so_line_id" attrs="{'readonly': 1}" optional="hide"/>
<field name="so_id" attrs="{'readonly': 1}" optional="hide"/>
<field name="indent" optional="hide"/>
@@ -129,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">
@@ -290,11 +301,14 @@
<field name="partner_invoice_id"/>
<field name="salesperson_id"/>
<field name="product_id"/>
+ <field name="purchase_price_so"/>
+ <field name="purchase_price_po"/>
<field name="qty_so"/>
<field name="qty_po"/>
<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>
@@ -319,6 +333,15 @@
</record>
</data>
<data>
+ <record id="purchase_order_multi_ask_approval_ir_actions_server" model="ir.actions.server">
+ <field name="name">Ask Approval PO</field>
+ <field name="model_id" ref="purchase.model_purchase_order"/>
+ <field name="binding_model_id" ref="purchase.model_purchase_order"/>
+ <field name="state">code</field>
+ <field name="code">action = records.open_form_multi_ask_approval_po()</field>
+ </record>
+ </data>
+ <data>
<record id="purchase_order_multi_create_uangmuka_ir_actions_server" model="ir.actions.server">
<field name="name">Uang Muka</field>
<field name="model_id" ref="purchase.model_purchase_order"/>
diff --git a/indoteknik_custom/views/purchase_order_multi_ask_approval.xml b/indoteknik_custom/views/purchase_order_multi_ask_approval.xml
new file mode 100644
index 00000000..887ac0bd
--- /dev/null
+++ b/indoteknik_custom/views/purchase_order_multi_ask_approval.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<odoo>
+ <data>
+ <record id="view_purchase_order_multi_ask_approval_po_form" model="ir.ui.view">
+ <field name="name">Purchase Order Multi ask_approval</field>
+ <field name="model">purchase.order.multi_ask_approval</field>
+ <field name="arch" type="xml">
+ <form>
+ <sheet>
+ <group>
+ <span>Apakah Anda Yakin Ingin Ask Approval PO Tersebut?</span>
+ </group>
+ </sheet>
+ <footer>
+ <button name="save_multi_ask_approval_po" string="Ask Approval PO" type="object" default_focus="1" class="oe_highlight"/>
+ <button string="Cancel" class="btn btn-secondary" special="cancel" />
+ </footer>
+ </form>
+ </field>
+ </record>
+
+ <record id="action_purchase_order_multi_ask_approval" model="ir.actions.act_window">
+ <field name="name">Purchase Order Multi ask_approval</field>
+ <field name="res_model">purchase.order.multi_ask_approval</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="view_mode">form</field>
+ <field name="view_id" ref="view_purchase_order_multi_ask_approval_po_form"/>
+ <field name="target">new</field>
+ </record>
+ </data>
+</odoo> \ No newline at end of file
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/report_logbook_sj.xml b/indoteknik_custom/views/report_logbook_sj.xml
index 896594bb..94f6c2ab 100644
--- a/indoteknik_custom/views/report_logbook_sj.xml
+++ b/indoteknik_custom/views/report_logbook_sj.xml
@@ -77,6 +77,16 @@
</field>
</record>
+ <record id="report_logbook_sj_view_search" model="ir.ui.view">
+ <field name="name">report.logbook.sj.search.view</field> <!-- Made the name more descriptive -->
+ <field name="model">report.logbook.sj</field>
+ <field name="arch" type="xml">
+ <search string="Search Report">
+ <field name="sj_number"/>
+ </search>
+ </field>
+ </record>
+
<record id="report_logbook_sj_action" model="ir.actions.act_window">
<field name="name">Report Logbook SJ</field>
<field name="type">ir.actions.act_window</field>
diff --git a/indoteknik_custom/views/res_partner.xml b/indoteknik_custom/views/res_partner.xml
index 19d4b940..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"/>
diff --git a/indoteknik_custom/views/res_users.xml b/indoteknik_custom/views/res_users.xml
index 9553bb91..5b35f9c4 100644
--- a/indoteknik_custom/views/res_users.xml
+++ b/indoteknik_custom/views/res_users.xml
@@ -236,6 +236,7 @@
<tr><td style="padding-bottom: 16px;"><b>Pembayaran Lengkap:</b> Pilih metode pembayaran yang sesuai dengan kebutuhan Anda, baik transfer bank, VA, kartu kredit, atau pembayaran tempo.</td></tr>
<tr><td style="padding-bottom: 16px;"><b>Pelacakan Pengiriman:</b> Lacak status pengiriman pesanan Anda secara real-time.</td></tr>
<tr><td style="padding-bottom: 16px;">Untuk memulai transaksi, silakan login Kembali menggunakan akun Anda di <a href="https://indoteknik.com/login">Indoteknik.com</a></td></tr>
+ <tr><td style="padding-bottom: 16px;">Gunakan kode voucher berikut untuk mendapatkan diskon belanja hingga 10 juta: <strong>${object.get_voucher_code('switch_account')}</strong></td></tr>
<tr><td style="padding-bottom: 16px;">Kami sangat senang dapat melayani Anda. Jika ada pertanyaan atau membutuhkan bantuan, jangan ragu untuk menghubungi tim layanan Customer kami di Whatsapp <a href="https://wa.me/6281717181922">0817-1718-1922</a> atau <a href="mailto:sales@indoteknik.com">sales@indoteknik.com</a></td></tr>
<tr><td style="padding-bottom: 2px;"><b>Hormat kami,</b></td></tr>
@@ -299,6 +300,7 @@
<tr><td style="padding-bottom: 16px;">Selamat! Akun bisnis Anda di indoteknik.com sekarang sudah resmi menjadi PKP dari yang sebelumnya Non-PKP.</td></tr>
<tr><td style="padding-bottom: 16px;">Jangan lupa untuk mengecek kembali semua data perusahaan kamu, ya. Pastikan NPWP dan informasi Perusahaan lainnya sudah kamu isi dengan benar.</td></tr>
+ <tr><td style="padding-bottom: 16px;">Gunakan kode voucher berikut untuk mendapatkan diskon belanja hingga 10 juta: <strong>${object.get_voucher_code('switch_account')}</strong></td></tr>
<tr><td style="padding-bottom: 16px;">Kamu juga dapat mengubah informasi perusahaan dengan mengunjungi profil atau <a href="https://indoteknik.com/my/profile">klik disini</a></td></tr>
<tr><td style="padding-bottom: 2px;">Industrial Supply &amp; Solutions</td></tr>
diff --git a/indoteknik_custom/views/sale_order.xml b/indoteknik_custom/views/sale_order.xml
index 008a04ed..e0085eeb 100755
--- a/indoteknik_custom/views/sale_order.xml
+++ b/indoteknik_custom/views/sale_order.xml
@@ -1,174 +1,225 @@
-<?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="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">
- <attribute name="attrs">
+ <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 +227,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 +307,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,60 +343,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>
+
+ <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="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>
@@ -330,46 +436,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>
@@ -381,45 +522,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>
@@ -431,9 +586,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>
@@ -442,8 +597,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>
@@ -452,66 +607,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_backorder_confirmation_views.xml b/indoteknik_custom/views/stock_backorder_confirmation_views.xml
new file mode 100644
index 00000000..a622899e
--- /dev/null
+++ b/indoteknik_custom/views/stock_backorder_confirmation_views.xml
@@ -0,0 +1,14 @@
+<odoo>
+ <record id="view_backorder_confirmation_inherit" model="ir.ui.view">
+ <field name="name">stock_backorder_confirmation_inherit</field>
+ <field name="model">stock.backorder.confirmation</field>
+ <field name="inherit_id" ref="stock.view_backorder_confirmation"/>
+ <field name="arch" type="xml">
+ <xpath expr="//group/p[1]" position="replace">
+ <p>
+ Ada product yang quantity done nya kurang dari quantity demand.
+ </p>
+ </xpath>
+ </field>
+ </record>
+</odoo>
diff --git a/indoteknik_custom/views/stock_inventory.xml b/indoteknik_custom/views/stock_inventory.xml
new file mode 100644
index 00000000..db85f05c
--- /dev/null
+++ b/indoteknik_custom/views/stock_inventory.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<odoo>
+ <!-- Form View: Tambahkan field 'number' setelah lokasi -->
+ <record id="view_stock_inventory_form_inherit" model="ir.ui.view">
+ <field name="name">stock.inventory.form.inherit</field>
+ <field name="model">stock.inventory</field>
+ <field name="inherit_id" ref="stock.view_inventory_form"/>
+ <field name="arch" type="xml">
+ <xpath expr="//field[@name='location_ids']" position="after">
+ <field name="number" readonly="1"/>
+ <field name="adjusment_type" />
+ </xpath>
+ </field>
+ </record>
+
+ <!-- Tree View: Tambahkan field 'number' setelah tanggal -->
+ <record id="view_stock_inventory_tree_inherit" model="ir.ui.view">
+ <field name="name">stock.inventory.tree.inherit</field>
+ <field name="model">stock.inventory</field>
+ <field name="inherit_id" ref="stock.view_inventory_tree"/>
+ <field name="arch" type="xml">
+ <xpath expr="//field[@name='date']" position="after">
+ <field name="number"/>
+ </xpath>
+ </field>
+ </record>
+
+</odoo>
diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml
index 8acba608..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>
@@ -86,19 +114,22 @@
<field name="partner_id" position="after">
<field name="real_shipping_id"/>
</field>
- <field name="product_uom_qty" position="attributes">
+ <!-- <field name="product_uom_qty" position="attributes">
<attribute name="attrs">{'readonly': [('parent.picking_type_code', '=', 'incoming')]}</attribute>
- </field>
+ </field> -->
<field name="date_done" position="after">
<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"/>
@@ -132,6 +164,7 @@
<field name="sale_id" attrs="{'readonly': 1}" optional="hide"/>
<field name="print_barcode" optional="hide"/>
<field name="qr_code_variant" widget="image" optional="hide"/>
+ <field name="barcode" optional="hide"/>
</field>
<page name="note" position="after">
<page string="E-Faktur" name="efaktur" attrs="{'invisible': [['is_internal_use', '=', False]]}">
@@ -143,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')]}"/>
@@ -189,27 +224,92 @@
<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>
+ <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="check_product_tree" model="ir.ui.view">
+ <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>
+
+ <record id="barcode_product_tree" model="ir.ui.view">
+ <field name="name">barcode.product.tree</field>
+ <field name="model">barcode.product</field>
+ <field name="arch" type="xml">
+ <tree editable="bottom">
+ <field name="product_id"/>
+ <field name="barcode"/>
</tree>
</field>
- </record> -->
+ </record>
<record id="view_stock_move_line_detailed_operation_tree_inherit" model="ir.ui.view">
<field name="name">stock.move.line.operations.tree.inherit</field>
@@ -232,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_pengajuan_tempo.xml b/indoteknik_custom/views/user_pengajuan_tempo.xml
index 33ad91cf..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"/>
@@ -269,7 +271,7 @@
<field name="subject">Pengajuan Tempo Harus di Periksa!</field>
<field name="email_from">"Indoteknik.com" &lt;noreply@indoteknik.com&gt;</field>
<field name="reply_to">sales@indoteknik.com</field>
- <field name="email_to">widyariyanti97@gmail.com, stephan@indoteknik.co.id</field>
+ <field name="email_to">finance@indoteknik.co.id, stephan@indoteknik.co.id, sapiabon768@gmail.com</field>
<!-- <field name="email_to">sapiabon768@gmail.com</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;">
diff --git a/indoteknik_custom/views/user_pengajuan_tempo_request.xml b/indoteknik_custom/views/user_pengajuan_tempo_request.xml
index bb8262c9..339ce8db 100644
--- a/indoteknik_custom/views/user_pengajuan_tempo_request.xml
+++ b/indoteknik_custom/views/user_pengajuan_tempo_request.xml
@@ -10,7 +10,17 @@
<field name="create_date"/>
<field name="tempo_duration" attrs="{'readonly': [('state_tempo', '=', 'approval_director')]}"/>
<field name="tempo_limit" attrs="{'readonly': [('state_tempo', '=', 'approval_director')]}" placeholder="Contoh format, misalnya '10000000'" widget="monetary" options="{'currency_field': 'currency_id'}"/>
- <field name="state_tempo" decoration-success="state_tempo == 'approval_director'" decoration-danger="state_tempo == 'reject'" widget="badge" optional="show"/>
+<!-- <field name="state_tempo"-->
+<!-- decoration-danger="state_tempo == 'reject'"-->
+<!-- decoration-warning="state_tempo in ('draft', 'approval_sales', 'approval_finance')"-->
+<!-- decoration-success="state_tempo == 'approval_director'">-->
+<!-- <tree>-->
+<!-- <field name="state_tempo" invisible="1"/>-->
+<!-- <field name="state_tempo_text" string="Status"/>-->
+<!-- </tree>-->
+<!-- </field>-->
+ <field name="state_tempo" invisible="1"/>
+ <field name="state_tempo_text" decoration-success="state_tempo_text == 'Approved'" decoration-danger="state_tempo_text == 'Rejected'" widget="badge" optional="show"/>
</tree>
</field>
</record>
@@ -30,7 +40,11 @@
string="Reject"
attrs="{'invisible': [('state_tempo', 'in', ['approval_director','reject'])]}"
type="object"
- groups="purchase.group_purchase_manager"
+ class="oe_highlight"/>
+ <button name="button_draft"
+ string="Reset to Draft"
+ attrs="{'invisible': [('state_tempo', '!=', 'reject')]}"
+ type="object"
class="oe_highlight"/>
<field name="state_tempo" widget="statusbar"
statusbar_visible="draft,approval_sales,approval_finance,approval_director"
@@ -88,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"/>
@@ -97,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"/>
@@ -107,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 29d0ad4b..78e42969 100755
--- a/indoteknik_custom/views/voucher.xml
+++ b/indoteknik_custom/views/voucher.xml
@@ -27,62 +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="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>
@@ -91,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>