diff options
| author | trisusilo48 <tri.susilo@altama.co.id> | 2024-10-02 09:44:39 +0700 |
|---|---|---|
| committer | trisusilo48 <tri.susilo@altama.co.id> | 2024-10-02 09:44:39 +0700 |
| commit | 2739d3040a69228192096ee16373610149a2fb47 (patch) | |
| tree | 545aa797d7407e8f22250dff15648495b891928f | |
| parent | 4d3d219b5f1002822a16067a28261fd59b170ff8 (diff) | |
| parent | 7d3780ede67579b5891218efc370dc82eef510a1 (diff) | |
Merge branch 'production' of https://bitbucket.org/altafixco/indoteknik-addons into production
50 files changed, 1840 insertions, 212 deletions
diff --git a/indoteknik_api/controllers/api_v1/cart.py b/indoteknik_api/controllers/api_v1/cart.py index f472a9b0..2a24b205 100644 --- a/indoteknik_api/controllers/api_v1/cart.py +++ b/indoteknik_api/controllers/api_v1/cart.py @@ -16,10 +16,28 @@ class Cart(controller.Controller): offset = int(kw.get('offset', 0)) query = [('user_id', '=', user_id)] carts = user_cart.search(query, limit=limit, offset=offset, order='create_date desc') - carts.write({'source': 'add_to_cart'}) + # carts.write({'source': 'add_to_cart'}) + products = [] + products_inactive = [] + for cart in carts: + if cart.product_id: + price = cart.product_id._v2_get_website_price_include_tax() + if cart.product_id.active and price > 0: + product = cart.with_context(price_for="web").get_products() + for product_active in product: + products.append(product_active) + else: + product_inactives = cart.with_context(price_for="web").get_products() + for inactives in product_inactives: + products_inactive.append(inactives) + else: + program = cart.with_context(price_for="web").get_products() + for programs in program: + products.append(programs) data = { 'product_total': user_cart.search_count(query), - 'products': carts.with_context(price_for="web").get_products() + 'products': products, + 'products_inactive': products_inactive } return self.response(data) diff --git a/indoteknik_api/controllers/api_v1/category_management.py b/indoteknik_api/controllers/api_v1/category_management.py index 836f4493..c0ecc6b9 100644 --- a/indoteknik_api/controllers/api_v1/category_management.py +++ b/indoteknik_api/controllers/api_v1/category_management.py @@ -2,6 +2,7 @@ from odoo import http from odoo.http import request from .. import controller + class CategoryManagement(controller.Controller): prefix = '/api/v1/' @@ -42,5 +43,3 @@ class CategoryManagement(controller.Controller): 'categories': category_id2_data, }) return self.response(data, headers=[('Cache-Control', 'max-age=3600, public')]) - - diff --git a/indoteknik_api/controllers/api_v1/partner.py b/indoteknik_api/controllers/api_v1/partner.py index 69a2f861..369f2125 100644 --- a/indoteknik_api/controllers/api_v1/partner.py +++ b/indoteknik_api/controllers/api_v1/partner.py @@ -66,11 +66,13 @@ class Partner(controller.Controller): 'name': ['required'], 'email': ['required'], 'mobile': ['required'], + 'phone': [''], 'street': ['required'], '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'], + 'alamat_lengkap_text': [] }) if not params['valid']: @@ -95,6 +97,7 @@ class Partner(controller.Controller): 'name': ['required'], 'email': ['required'], 'mobile': ['required'], + 'phone': [''], 'street': ['required'], 'city_id': ['required', 'number', 'alias:kota_id'], 'district_id': ['number', 'alias:kecamatan_id'], @@ -115,23 +118,55 @@ class Partner(controller.Controller): @controller.Controller.must_authorized() def write_partner_by_id(self, **kw): params = self.get_request_params(kw, { - 'id': ['required', 'number'], + 'id': ['', 'number'], 'name': [], 'company_type_id': ['number'], 'industry_id': ['number'], 'tax_name': ['alias:nama_wajib_pajak'], 'npwp': [], + 'alamat_lengkap_text': [], + 'street': [], + }) + + # Mengambil id_user dari request + id_user = self.get_request_params(kw, { + 'id_user': ['number'] + }) + + # Mengambil parameter user dari request + params_user = self.get_request_params(kw, { + 'company_type_id': ['number'], + 'industry_id': ['number'], + 'tax_name': ['alias:nama_wajib_pajak'], + 'npwp': [], + 'alamat_lengkap_text': [], }) + # Cek validitas parameter if not params['valid']: return self.response(code=400, description=params) - + + # Mencari partner dan user berdasarkan ID partner = request.env[self._name].search([('id', '=', params['value']['id'])], limit=1) + user = request.env[self._name].search([('id', '=', id_user['value']['id_user'])], limit=1) + if not partner: - return self.response(code=404, description='User not found') - - partner.write(params['value']) + return self.response(code=404, description='Partner not found') + + # Filter parameter yang memiliki nilai saja untuk partner + params_filtered = {k: v for k, v in params['value'].items() if v} + + # Filter parameter yang memiliki nilai saja untuk user + params_user_filtered = {k: v for k, v in params_user['value'].items() if v} + # Update partner dan user hanya dengan parameter yang memiliki nilai + if params_filtered: + partner.write(params_filtered) + + if params_user_filtered: + user.write(params_user_filtered) + + # Return response dengan ID partner yang di-update return self.response({ 'id': partner.id }) @@ -164,4 +199,32 @@ class Partner(controller.Controller): }) return self.response(data) + + @http.route(prefix + 'check/<partner_id>/tempo', auth='public', methods=['GET', 'OPTIONS']) + @controller.Controller.must_authorized() + def get_check_tempo_partner(self, **kw): + partner_id = int(kw.get('partner_id')) + + partner = request.env['res.partner'].search([('id', '=', partner_id)], limit=1) + if not partner: + return self.response(code=404, description='Partner not found') + + partner = partner.parent_id or partner + + if any(line.days == 0 for line in partner.property_payment_term_id.line_ids): + return self.response(code=402, description='Partner not tempo') + + result_tempo = sum(m.amount_total_signed for m in request.env['account.move'].search([('partner_id', '=', partner.id), ('payment_state', '=', 'not_paid'), ('state', '=', 'posted')])) + + remaining_limit = partner.blocking_stage - result_tempo if partner.active_limit else None + + data = { + 'name': partner.name, + 'amount_due': result_tempo, + 'remaining_limit': remaining_limit + } + + return self.response(data) + + diff --git a/indoteknik_api/controllers/api_v1/sale_order.py b/indoteknik_api/controllers/api_v1/sale_order.py index 1b7c1af4..e7664c79 100644 --- a/indoteknik_api/controllers/api_v1/sale_order.py +++ b/indoteknik_api/controllers/api_v1/sale_order.py @@ -388,6 +388,9 @@ class SaleOrder(controller.Controller): if not params['valid']: return self.response(code=400, description=params) + # Fetch partner details + sales_partner = request.env['res.partner'].browse(params['value']['partner_id']) + parameters = { 'warehouse_id': 8, 'carrier_id': 1, @@ -407,7 +410,7 @@ class SaleOrder(controller.Controller): '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'], + 'delivery_amt': params['value']['delivery_amount'] * 1.10, 'estimated_arrival_days': params['value']['estimated_arrival_days'], 'shipping_cost_covered': 'customer', 'shipping_paid_by': 'customer', @@ -415,9 +418,11 @@ class SaleOrder(controller.Controller): 'delivery_service_type': params['value']['delivery_service_type'], 'flash_sale': params['value']['flash_sale'], 'note_website': params['value']['note_website'], - 'customer_type': 'nonpkp', - 'npwp': '0', - 'user_id': 3222 # User ID: Nadia Rauhadatul Firdaus + 'customer_type': sales_partner.customer_type if sales_partner else 'nonpkp', # Get Customer Type from partner + '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 } sales_partner = request.env['res.partner'].browse(parameters['partner_id']) @@ -460,11 +465,16 @@ class SaleOrder(controller.Controller): sale_order.apply_promotion_program() voucher_code = params['value']['voucher'] - voucher = request.env['voucher'].search([('code', '=', voucher_code)]) + voucher = request.env['voucher'].search([('code', '=', voucher_code),('apply_type', 'in', ['all', 'brand'])], limit=1) + voucher_shipping = request.env['voucher'].search([('code', '=', voucher_code),('apply_type', 'in', ['shipping'])], limit=1) if voucher and len(promotions) == 0: sale_order.voucher_id = voucher.id sale_order.apply_voucher() + if voucher_shipping and len(promotions) == 0: + sale_order.voucher_shipping_id = voucher_shipping.id + sale_order.apply_voucher_shipping() + cart_ids = [x['cart_id'] for x in carts] if sale_order._requires_approval_margin_leader(): #jika ada error tambahkan kondisi if params['value']['type'] == 'sale_order': sale_order.approval_status = 'pengajuan2' diff --git a/indoteknik_api/controllers/api_v1/user.py b/indoteknik_api/controllers/api_v1/user.py index 10508149..529873c0 100644 --- a/indoteknik_api/controllers/api_v1/user.py +++ b/indoteknik_api/controllers/api_v1/user.py @@ -9,6 +9,8 @@ import string import requests import json from difflib import SequenceMatcher +import mimetypes +import base64 class User(controller.Controller): @@ -105,12 +107,28 @@ class User(controller.Controller): name = kw.get('name') email = kw.get('email') password = kw.get('password') - if not name or not email or not password: - return self.response(code=400, description='email, name and password is required') - - company = kw.get('company', False) phone = kw.get('phone') + # Form Data + npwp = kw.get('npwp') + npwp_document = kw.get('npwp_document', False) + npwp_filename = kw.get('npwp_filename', 'npwp_document') + sppkp = kw.get('sppkp', False) + sppkp_document = kw.get('sppkp_document', False) + sppkp_filename = kw.get('sppkp_filename', 'sppkp_document') + email_partner = kw.get('email_partner') + business_name = kw.get('business_name', False) + industry_id = int(kw.get('industry_id', '0') or 0) + company_type_id = int(kw.get('company_type_id', '0') or 0) + alamat_wajib_pajak = kw.get('alamat_wajib_pajak', False) + alamat_bisnis = kw.get('alamat_bisnis', False) + nama_wajib_pajak = kw.get('nama_wajib_pajak', False) + is_pkp = kw.get('is_pkp') + type_acc = kw.get('type_acc', 'individu') or 'individu' + + if not name or not email or not password: + return self.response(code=400, description='email, name, and password are required') + response = { 'register': False, 'reason': None @@ -126,10 +144,11 @@ class User(controller.Controller): return self.response(response) + # Create user data user_data = { 'name': name, 'login': email, - 'phone': phone, + 'mobile': phone, 'password': password, 'active': False, 'sel_groups_1_9_10': 9 @@ -137,27 +156,97 @@ class User(controller.Controller): user = request.env['res.users'].create(user_data) user.partner_id.email = email - - if company: - parameter = [ - ('company_type', '=', 'company'), - ('name', 'ilike', company) - ] - match_company = request.env['res.partner'].search(parameter, limit=1) - match_ratio = 0 - if match_company: - match_ratio = SequenceMatcher(None, match_company.name, company).ratio() - if match_ratio > 0.8: + user.partner_id.mobile = phone + + if type_acc == 'business' and business_name: + # Eksekusi query SQL menggunakan Levenshtein distance + query = """ + SELECT name, levenshtein(name::text, %s) AS distance + FROM res_partner + WHERE levenshtein(name::text, %s) < 3 + ORDER BY distance ASC + """ + params = (business_name, business_name) + request.env.cr.execute(query, params) + result = request.env.cr.fetchone() + + if result: + match_company_name = result[0] + match_company_id = result[2] + + # Create a user company request request.env['user.company.request'].create({ 'user_id': user.partner_id.id, - 'user_company_id': match_company.id, - 'user_input': company + 'user_company_id': match_company_id, + 'user_input': business_name }) else: - new_company = request.env['res.partner'].create({ - 'name': company + if not nama_wajib_pajak and is_pkp == 'false': + nama_wajib_pajak = business_name + + if not alamat_wajib_pajak and is_pkp == 'false': + alamat_wajib_pajak = alamat_bisnis + + new_company_data = { + 'name': business_name if business_name else business_name, + 'company_type_id': company_type_id if company_type_id else False, + 'industry_id': industry_id, + 'customer_type': 'pkp' if is_pkp == 'true' else 'nonpkp', + 'npwp': npwp if is_pkp == 'true' else (npwp if npwp else '00.000.000.0-000.000'), + 'sppkp': sppkp if is_pkp == 'true' else (sppkp if sppkp else '-'), + 'nama_wajib_pajak': nama_wajib_pajak, + 'alamat_lengkap_text': alamat_wajib_pajak, + 'email': email_partner, + 'street': alamat_bisnis, + 'company_type': 'company', + 'user_id': 3222, + 'property_account_receivable_id': 395, + 'property_account_payable_id': 438, + 'active': False, + } + new_company = request.env['res.partner'].create(new_company_data) + request.env['user.company.request'].create({ + 'user_id': user.partner_id.id, + 'user_company_id': new_company.id, + 'user_input': business_name }) - user.parent_id = new_company.id + + if npwp_document: + npwp_mimetype, _ = mimetypes.guess_type(npwp_filename) + npwp_mimetype = npwp_mimetype or 'application/octet-stream' + pdf_data = base64.b64decode(npwp_document) + npwp_attachment = request.env['ir.attachment'].create({ + 'name': 'NPWP Document', + 'type': 'binary', + 'datas': base64.b64encode(pdf_data), + 'res_model': 'res.partner', + 'res_id': new_company.id, + 'mimetype': npwp_mimetype + }) + new_company.message_post(body="NPWP Uploaded", attachment_ids=[npwp_attachment.id]) + + if sppkp_document: + sppkp_mimetype, _ = mimetypes.guess_type(sppkp_filename) + sppkp_mimetype = sppkp_mimetype or 'application/octet-stream' + pdf_data = base64.b64decode(sppkp_document) + sppkp_attachment = request.env['ir.attachment'].create({ + 'name': 'SPPKP Document', + 'type': 'binary', + 'datas': base64.b64encode(pdf_data), + 'res_model': 'res.partner', + 'res_id': new_company.id, + 'mimetype': sppkp_mimetype + }) + new_company.message_post(body="SPPKP Uploaded", attachment_ids=[sppkp_attachment.id]) + + if type_acc == 'individu': + user.partner_id.customer_type = 'nonpkp' + user.partner_id.npwp = '0.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.property_account_receivable_id= 395 + user.partner_id.property_account_payable_id = 438 user.send_activation_mail() @@ -240,6 +329,14 @@ class User(controller.Controller): 'activation': True, 'user': self.response_with_token(user) }) + parameter = [ + ('user_id', '=', user.partner_id.id) + ] + company_request = request.env['user.company.request'].search(parameter, limit=1) + if company_request: + user.parent_name = company_request.user_input + if company_request.is_approve == False: + user.send_company_request_mail() return self.response(response) @http.route(prefix + 'user/forgot-password', auth='public', methods=['POST'], csrf=False) @@ -315,3 +412,144 @@ class User(controller.Controller): x) for x in partners] return self.response(address) + + @http.route(prefix + 'user/<id>/switch', auth='public', methods=['PUT', 'OPTIONS'], csrf=False) + @controller.Controller.must_authorized() + def switch_account(self, **kw): + id = kw.get('id') + user = request.env['res.users'].search([('id', '=', id)], limit=1) + + response = { + 'switch': False, + 'reason': None + } + + # Form Data + npwp = kw.get('npwp') + npwp_document = kw.get('npwp_document', False) + npwp_filename = kw.get('npwp_filename', 'npwp_document') + sppkp = kw.get('sppkp', False) + sppkp_document = kw.get('sppkp_document', False) + sppkp_filename = kw.get('sppkp_filename', 'sppkp_document') + email_partner = kw.get('email_partner') + business_name = kw.get('business_name', False) + industry_id = int(kw.get('industry_id', '0') or 0) + company_type_id = int(kw.get('company_type_id', '0') or 0) + alamat_wajib_pajak = kw.get('alamat_wajib_pajak', False) + alamat_bisnis = kw.get('alamat_bisnis', False) + nama_wajib_pajak = kw.get('nama_wajib_pajak', False) + is_pkp = kw.get('is_pkp') + type_acc = kw.get('type_acc', False) + + + response = { + 'switch': False, + 'reason': None + } + + if type_acc == 'business': + parameter = [ + ('company_type', '=', 'company'), + ('name', 'ilike', business_name) + ] + match_company = request.env['res.partner'].search(parameter, limit=1) + match_ratio = 0 + if match_company: + match_ratio = SequenceMatcher(None, match_company.name, business_name).ratio() + if match_ratio > 0.8: + # Create a user company request + request.env['user.company.request'].create({ + 'user_id': user.partner_id.id, + 'user_company_id': match_company.id, + 'user_input': business_name + }) + else: + if not nama_wajib_pajak and is_pkp == 'false': + nama_wajib_pajak = business_name + + if not alamat_wajib_pajak and is_pkp == 'false': + alamat_wajib_pajak = alamat_bisnis + + new_company_data = { + 'name': business_name if business_name else business_name, + 'company_type_id': company_type_id if company_type_id else False, + 'industry_id': industry_id, + 'customer_type': 'pkp' if is_pkp == 'true' else 'nonpkp', + 'npwp': npwp if is_pkp == 'true' else (npwp if npwp else '00.000.000.0-000.000'), + 'sppkp': sppkp if is_pkp == 'true' else (sppkp if sppkp else '-'), + 'nama_wajib_pajak': nama_wajib_pajak, + 'alamat_lengkap_text': alamat_wajib_pajak, + 'email': email_partner, + 'street': alamat_bisnis, + 'company_type': 'company', + 'user_id': 3222, + 'property_account_receivable_id': 395, + 'property_account_payable_id': 438, + 'active': False, + } + new_company = request.env['res.partner'].create(new_company_data) + request.env['user.company.request'].create({ + 'user_id': user.partner_id.id, + 'user_company_id': new_company.id, + 'user_input': business_name + }) + # user.partner_id.parent_id = new_company.id + # user.partner_id.customer_type = new_company.customer_type + # user.partner_id.npwp = new_company.npwp + # user.partner_id.sppkp = new_company.sppkp + # user.partner_id.nama_wajib_pajak = new_company.nama_wajib_pajak + # user.partner_id.alamat_lengkap_text = new_company.alamat_lengkap_text + + if npwp_document: + npwp_mimetype, _ = mimetypes.guess_type(npwp_filename) + npwp_mimetype = npwp_mimetype or 'application/octet-stream' + pdf_data = base64.b64decode(npwp_document) + npwp_attachment = request.env['ir.attachment'].create({ + 'name': 'NPWP Document', + 'type': 'binary', + 'datas': base64.b64encode(pdf_data), + 'res_model': 'res.partner', + 'res_id': new_company.id, + 'mimetype': npwp_mimetype + }) + new_company.message_post(body="NPWP Uploaded", attachment_ids=[npwp_attachment.id]) + + if sppkp_document: + sppkp_mimetype, _ = mimetypes.guess_type(sppkp_filename) + sppkp_mimetype = sppkp_mimetype or 'application/octet-stream' + pdf_data = base64.b64decode(sppkp_document) + sppkp_attachment = request.env['ir.attachment'].create({ + 'name': 'SPPKP Document', + 'type': 'binary', + 'datas': base64.b64encode(pdf_data), + 'res_model': 'res.partner', + 'res_id': new_company.id, + 'mimetype': sppkp_mimetype + }) + new_company.message_post(body="SPPKP Uploaded", attachment_ids=[sppkp_attachment.id]) + + response['switch'] = 'Pending' + return self.response(response) + + @http.route(prefix + 'user/<id>/switch_progres', auth='public', methods=['GET', 'OPTIONS'], csrf=False) + # @controller.Controller.must_authorized() + def switch_account_progres(self, **kw): + id = int(kw.get('id')) + user = request.env['res.users'].search([('id', '=', id)], limit=1) + response = { + 'status': '' + } + parameter = [ + ('user_id', '=', id) + ] + new_company_request = request.env['user.company.request'].search(parameter, limit=1) + is_approve = '' + if new_company_request: + # Mengambil nilai is_approve + if new_company_request.is_approve == False: + response['status'] = 'pending' + else: + response['status'] = new_company_request.is_approve + else: + response['status'] = 'unknown' + return self.response(response)
\ No newline at end of file diff --git a/indoteknik_api/models/res_users.py b/indoteknik_api/models/res_users.py index d5dff876..b2e8acfe 100644 --- a/indoteknik_api/models/res_users.py +++ b/indoteknik_api/models/res_users.py @@ -43,6 +43,7 @@ class ResUsers(models.Model): 'type': user.type or '', 'name': user.name or '', 'mobile': user.mobile or '', + 'phone': user.phone or '', 'email': user.email or '', 'street': user.street or '', 'street2': user.street2 or '', @@ -55,6 +56,8 @@ class ResUsers(models.Model): 'tax_name': user.nama_wajib_pajak or '', 'npwp': user.npwp or '', 'rajaongkir_city_id': user.kecamatan_id.rajaongkir_id or 0, + 'alamat_wajib_pajak': user.alamat_lengkap_text or None, + 'alamat_bisnis': user.street or None, } if user.kota_id: diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py index 8d8e8cec..e1a67592 100755 --- a/indoteknik_custom/__manifest__.py +++ b/indoteknik_custom/__manifest__.py @@ -142,6 +142,7 @@ 'views/approval_date_doc.xml', 'views/partner_payment_term.xml', 'views/vendor_payment_term.xml', + 'views/approval_unreserve.xml', 'report/report.xml', 'report/report_banner_banner.xml', 'report/report_banner_banner2.xml', diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py index e9ce587c..3d700ce0 100755 --- a/indoteknik_custom/models/__init__.py +++ b/indoteknik_custom/models/__init__.py @@ -39,6 +39,7 @@ from . import website_brand_homepage from . import website_categories_homepage from . import website_categories_lob from . import website_categories_management +from . import website_categories_management_line from . import website_content from . import website_page_content from . import website_user_cart @@ -125,3 +126,5 @@ from . import sale_order_multi_uangmuka_penjualan from . import shipment_group from . import sales_order_reject from . import approval_date_doc +from . import account_tax +from . import approval_unreserve diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index a7010bbe..725b3c2d 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -174,7 +174,7 @@ class AccountMove(models.Model): def _compute_mark_upload_efaktur(self): for move in self: - if move.date_efaktur_exported or move.is_efaktur_exported or move.efaktur_document: + if move.efaktur_document: move.mark_upload_efaktur = 'sudah_upload' else: move.mark_upload_efaktur = 'belum_upload' diff --git a/indoteknik_custom/models/account_move_due_extension.py b/indoteknik_custom/models/account_move_due_extension.py index 0399c6a2..23f8888c 100644 --- a/indoteknik_custom/models/account_move_due_extension.py +++ b/indoteknik_custom/models/account_move_due_extension.py @@ -96,6 +96,8 @@ class DueExtension(models.Model): sales.action_confirm() self.order_id.due_id = self.id + template = self.env.ref('indoteknik_custom.mail_template_due_extension_approve') + template.send_mail(self.id, force_send=True) def generate_due_line(self): partners = self.partner_id.get_child_ids() diff --git a/indoteknik_custom/models/account_tax.py b/indoteknik_custom/models/account_tax.py new file mode 100644 index 00000000..e39546f2 --- /dev/null +++ b/indoteknik_custom/models/account_tax.py @@ -0,0 +1,22 @@ +from odoo import fields, models, api, _ +from odoo.exceptions import AccessError, UserError, ValidationError + +class AccountTax(models.Model): + _inherit = 'account.tax' + + @api.model + def create(self, vals): + group_id = self.env.ref('indoteknik_custom.group_role_it').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 IT yang bisa membuat tax') + result = super(AccountTax, self).create(vals) + return result + + def write(self, values): + group_id = self.env.ref('indoteknik_custom.group_role_it').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 IT yang bisa mengedit tax') + result = super(AccountTax, self).write(values) + return result
\ No newline at end of file diff --git a/indoteknik_custom/models/approval_unreserve.py b/indoteknik_custom/models/approval_unreserve.py new file mode 100644 index 00000000..88409c37 --- /dev/null +++ b/indoteknik_custom/models/approval_unreserve.py @@ -0,0 +1,146 @@ +from odoo import models, api, fields +from odoo.exceptions import AccessError, UserError, ValidationError +from datetime import timedelta, date +import logging + +_logger = logging.getLogger(__name__) + +class ApprovalUnreserve(models.Model): + _name = "approval.unreserve" + _description = "Approval Unreserve" + _inherit = ['mail.thread'] + _rec_name = 'number' + + number = fields.Char(string='Document No', index=True, copy=False, readonly=True, tracking=True, default='New') + approval_line = fields.One2many('approval.unreserve.line', 'approval_id', string='Approval Unreserve Lines', auto_join=True) + state = fields.Selection([ + ('draft', 'Draft'), + ('waiting_approval', 'Waiting for Approval'), + ('approved', 'Approved'), + ('rejected', 'Rejected') + ], string="Status", default='draft', tracking=True) + request_date = fields.Date(string="Request Date", default=fields.Date.today, tracking=True) + approved_by = fields.Many2one('res.users', string="Approved By", readonly=True, tracking=True) + picking_id = fields.Many2one('stock.picking', string="Picking", tracking=True) + user_id = fields.Many2one('res.users', string="User", readonly=True, tracking=True) + rejection_reason = fields.Text(string="Rejection Reason", tracking=True) + reason = fields.Text(string="Reason", tracking=True) + + @api.constrains('picking_id') + def create_move_id_line(self): + if not self.picking_id: + raise ValidationError("Picking is required") + + stock_move = self.env['stock.move'].search([('picking_id', '=', self.picking_id.id), ('state', '=', 'assigned')]) + + if not stock_move: + raise ValidationError("Picking is not found") + + for move in stock_move: + self.approval_line.create({ + 'approval_id': self.id, + 'move_id': move.id + }) + + self.user_id = self.picking_id.sale_id.user_id.id + + @api.model + def create(self, vals): + if vals.get('number', 'New') == 'New': + vals['number'] = self.env['ir.sequence'].next_by_code('approval.unreserve') or 'New' + return super(ApprovalUnreserve, self).create(vals) + + def action_submit_for_approval(self): + self._check_product_and_qty() + self.write({'state': 'waiting_approval'}) + + + def _check_product_and_qty(self): + stock_move = self.env['stock.move'] + for line in self.approval_line: + if line.dest_picking_id: + move = stock_move.search([ + ('picking_id', '=', line.dest_picking_id.id), + ('product_id', '=', line.product_id.id), + ('state', 'not in', ['done', 'cancel']) + ]) + + if not move: + raise UserError("Product tidak ada di destination picking") + + qty_unreserve = line.unreserve_qty + move.forecast_availability + + if move.product_uom_qty < qty_unreserve: + raise UserError("Quantity yang di unreserve melebihi quantity yang ada") + + def action_approve(self): + if self.env.user.id != self.user_id.id: + raise UserError("Hanya Sales nya yang bisa approve.") + + if self.state != 'waiting_approval': + raise UserError("Approval can only be done in 'Waiting for Approval' state") + + self.write({ + 'state': 'approved', + 'approved_by': self.env.user.id + }) + # Trigger the unreserve function + self._trigger_unreserve() + + def action_reject(self, reason): + if self.env.user.id != self.user_id.id: + raise UserError("Hanya Sales nya yang bisa reject.") + + if self.state != 'waiting_approval': + raise UserError("Rejection can only be done in 'Waiting for Approval' state") + self.write({ + 'state': 'rejected', + 'rejection_reason': reason + }) + + def _trigger_unreserve(self): + stock_move_obj = self.env['stock.move'] + + for line in self.approval_line: + move = stock_move_obj.browse(line.move_id.id) + move._do_unreserve(product=line.product_id, quantity=line.unreserve_qty) + + original_sale_id = move.picking_id.sale_id + + product_name = line.product_id.display_name + unreserved_qty = line.unreserve_qty + + if line.dest_picking_id: + dest_sale_id = line.dest_picking_id.sale_id + line.dest_picking_id.action_assign() + + if original_sale_id: + message = ( + f"Barang {product_name} sebanyak {unreserved_qty} dipindahkan ke SO {dest_sale_id.name}" + if dest_sale_id else + f"Barang {product_name} sebanyak {unreserved_qty} dipindahkan ke picking tujuan." + ) + original_sale_id.message_post(body=message) + else: + if original_sale_id: + message = f"Barang {product_name} sebanyak {unreserved_qty} ter unreserve." + original_sale_id.message_post(body=message) + + +class ApprovalUnreserveLine(models.Model): + _name = 'approval.unreserve.line' + _description = 'Approval Unreserve Line' + _order = 'approval_id, id' + + approval_id = fields.Many2one('approval.unreserve', string='Approval Reference', required=True, ondelete='cascade', index=True, copy=False) + move_id = fields.Many2one('stock.move', string="Stock Move", required=True) + product_id = fields.Many2one('product.product', string="Product", related='move_id.product_id', readonly=True) + sales_id = fields.Many2one('res.users', string="Sales", readonly=True, tracking=True) + unreserve_qty = fields.Float(string="Quantity to Unreserve") + dest_picking_id = fields.Many2one('stock.picking', string="Destination Picking", tracking=True) + + + @api.onchange('dest_picking_id') + def onchange_dest_picking_id(self): + if self.dest_picking_id: + self.sales_id = self.dest_picking_id.sale_id.user_id.id diff --git a/indoteknik_custom/models/automatic_purchase.py b/indoteknik_custom/models/automatic_purchase.py index 1d1322fa..548115e6 100644 --- a/indoteknik_custom/models/automatic_purchase.py +++ b/indoteknik_custom/models/automatic_purchase.py @@ -262,7 +262,7 @@ class AutomaticPurchase(models.Model): 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.update_purchase_price_so_line(line) self.create_purchase_order_sales_match(new_po) @@ -293,6 +293,9 @@ class AutomaticPurchase(models.Model): sale_ids_set.add(sale_id_with_salesperson) sale_ids_name.add(sale_order.sale_id.name) + margin_item = sale_order.sale_line_id.item_margin / sale_order.qty_so if sale_order.qty_so else 0 + margin_item = margin_item * sale_order.qty_po + matches_so_line = { 'purchase_order_id': purchase_order.id, 'sale_id': sale_order.sale_id.id, @@ -305,7 +308,8 @@ class AutomaticPurchase(models.Model): 'product_id': sale_order.product_id.id, 'qty_so': sale_order.qty_so, 'qty_po': sale_order.qty_po, - 'margin_so': sale_order.sale_line_id.item_percent_margin + 'margin_so': sale_order.sale_line_id.item_percent_margin, + 'margin_item': margin_item } po_matches_so_line = self.env['purchase.order.sales.match'].create([matches_so_line]) diff --git a/indoteknik_custom/models/bill_receipt.py b/indoteknik_custom/models/bill_receipt.py index 76449c1f..7d38d5ad 100644 --- a/indoteknik_custom/models/bill_receipt.py +++ b/indoteknik_custom/models/bill_receipt.py @@ -22,8 +22,16 @@ class BillReceipt(models.Model): resi_tukar_faktur = fields.Char(string='Resi Faktur') date_terima_tukar_faktur = fields.Date(string='Terima Faktur') shipper_faktur_id = fields.Many2one('delivery.carrier', string='Shipper Faktur') - is_validated = fields.Boolean(string='Validated') + is_validated = fields.Boolean(string='Validated') notification = fields.Char(string='Notification') + grand_total = fields.Float(string='Grand Total', compute="_compute_grand_total") + + def _compute_grand_total(self): + for record in self: + grand_total = 0 + for line in record.bill_line: + grand_total += line.total_amt + record.grand_total = grand_total def copy_date_faktur(self): if not self.is_validated: diff --git a/indoteknik_custom/models/commision.py b/indoteknik_custom/models/commision.py index c5809005..48f1c7f6 100644 --- a/indoteknik_custom/models/commision.py +++ b/indoteknik_custom/models/commision.py @@ -261,6 +261,7 @@ class CustomerCommision(models.Model): ('move_id.invoice_date', '>=', self.date_from), ('move_id.invoice_date', '<=', self.date_to), ('product_id.x_manufacture', 'in', brand), + ('exclude_from_invoice_tab', '=', False), ] invoice_lines = self.env['account.move.line'].search(where, order='id') for invoice_line in invoice_lines: diff --git a/indoteknik_custom/models/dunning_run.py b/indoteknik_custom/models/dunning_run.py index 90159cd0..c167aab7 100644 --- a/indoteknik_custom/models/dunning_run.py +++ b/indoteknik_custom/models/dunning_run.py @@ -30,7 +30,14 @@ class DunningRun(models.Model): is_paid = fields.Boolean(string='Paid') description = fields.Char(string='Description') comment = fields.Char(string='Comment') - + grand_total = fields.Float(string='Grand Total', compute="_compute_grand_total") + + def _compute_grand_total(self): + for record in self: + grand_total = 0 + for line in record.dunning_line: + grand_total += line.total_amt + record.grand_total = grand_total def copy_date_faktur(self): if not self.is_validated: diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py index e6778758..031d1b5b 100755 --- a/indoteknik_custom/models/product_template.py +++ b/indoteknik_custom/models/product_template.py @@ -338,9 +338,9 @@ class ProductTemplate(models.Model): return values def write(self, vals): - for rec in self: - if rec.id == 224484: - raise UserError('Tidak dapat mengubah produk sementara') + # for rec in self: + # if rec.id == 224484: + # raise UserError('Tidak dapat mengubah produk sementara') return super(ProductTemplate, self).write(vals) @@ -388,8 +388,16 @@ class ProductProduct(models.Model): if product_template.active and product.active: if not product.active and len(variants) == 1: product_template.with_context(skip_active_constraint=True).active = False + product_template.unpublished = True elif not product.active and len(variants) > 1: - continue + all_inactive = all(not variant.active for variant in variants) + if all_inactive: + product_template.with_context(skip_active_constraint=True).active = False + product_template.unpublished = True + else: + continue + if any(variant.active for variant in variants): + product_template.unpublished = False def update_internal_reference_variants(self, limit=100): variants = self.env['product.product'].search([ diff --git a/indoteknik_custom/models/promotion/promotion_program_line.py b/indoteknik_custom/models/promotion/promotion_program_line.py index a57f1f2c..7a15ad11 100644 --- a/indoteknik_custom/models/promotion/promotion_program_line.py +++ b/indoteknik_custom/models/promotion/promotion_program_line.py @@ -34,6 +34,29 @@ class PromotionProgramLine(models.Model): active = fields.Boolean(string="Active", default=True) solr_flag = fields.Integer(string="Solr Flag", default=1) description = fields.Char('Description') + price_tier_1 = fields.Float('Price Tier 1') + price_tier_2 = fields.Float('Price Tier 2') + price_tier_3 = fields.Float('Price Tier 3') + price_tier_4 = fields.Float('Price Tier 4') + price_tier_5 = fields.Float('Price Tier 5') + + def get_price_tier(self, product_id, qty): + product = self.env['product.product'].browse(product_id.id) + + tiers = ['1_v2', '2_v2', '3_v2', '4_v2', '5_v2'] + + for index, tier in enumerate(tiers, start=1): + + price_tier_data = product._get_pricelist_tier(tier) + + price_field = f'price_tier_{index}' + + if price_field in self._fields: + price = price_tier_data.get(f'price_tier{tier}', 0) * qty + self[price_field] = price + + if index == 1: + self.price = price def get_active_promotions(self, product_id): current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py index 8ec904a9..8a47482a 100755 --- a/indoteknik_custom/models/purchase_order.py +++ b/indoteknik_custom/models/purchase_order.py @@ -31,6 +31,7 @@ class PurchaseOrder(models.Model): ('approved', 'Approved'), ], string='Approval Status', readonly=True, copy=False, index=True, tracking=3) delivery_amount = fields.Float('Delivery Amount', compute='compute_delivery_amount') + delivery_amt = fields.Float('Delivery Amt') total_margin = fields.Float( 'Margin', compute='compute_total_margin', help="Total Margin in Sales Order Header") @@ -66,6 +67,72 @@ class PurchaseOrder(models.Model): ('printed', 'Printed') ], string='Printed?', copy=False, tracking=True) date_done_picking = fields.Datetime(string='Date Done Picking', compute='get_date_done') + bills_dp_id = fields.Many2one('account.move', string='Bills DP') + 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') + + def _compute_total_margin_match(self): + for purchase in self: + match = self.env['purchase.order.sales.match'] + result = match.read_group( + [('purchase_order_id', '=', purchase.id)], + ['margin_item'], + [] + ) + purchase.total_margin_match = result[0].get('margin_item', 0.0) + + def _compute_grand_total(self): + for order in self: + if order.delivery_amt: + order.grand_total = order.delivery_amt + order.amount_total + else: + order.grand_total = order.amount_total + + def create_bill_dp(self): + if not self.env.user.is_accounting: + raise UserError('Hanya Accounting yang bisa bikin bill dp') + + current_date = datetime.utcnow() + data_bills = { + 'partner_id': self.partner_id.id, + 'partner_shipping_id': self.partner_id.id, + 'ref': self.name, + 'invoice_date': current_date, + 'date': current_date, + 'move_type': 'in_invoice' + + } + + bills = self.env['account.move'].create([data_bills]) + + product_dp = self.env['product.product'].browse(229625) + + data_line_bills = { + 'move_id': bills.id, + 'product_id': product_dp.id, #product down payment + 'account_id': 401, #Uang Muka persediaan barang dagang + 'quantity': 1, + 'product_uom_id': 1, + 'tax_ids': [line[0].taxes_id.id for line in self.order_line], + } + + + bills_line = self.env['account.move.line'].create([data_line_bills]) + + self.bills_dp_id = bills.id + + move_line = bills.line_ids + move_line.name = '[IT.121456] Down Payment' + move_line.partner_id = self.partner_id.id + + return { + 'name': _('Account Move'), + 'view_mode': 'tree,form', + 'res_model': 'account.move', + 'target': 'current', + 'type': 'ir.actions.act_window', + 'domain': [('id', '=', bills.id)] + } def get_date_done(self): picking = self.env['stock.picking'].search([ @@ -624,6 +691,8 @@ class PurchaseOrder(models.Model): purchase_price = line.price_subtotal if line.order_id.delivery_amount > 0: purchase_price += line.delivery_amt_line + if line.order_id.delivery_amt > 0: + purchase_price += line.order_id.delivery_amt real_item_margin = sales_price - purchase_price sum_margin += real_item_margin @@ -666,6 +735,8 @@ class PurchaseOrder(models.Model): purchase_price = po_line.price_subtotal / po_line.product_qty * line.qty_po if line.purchase_order_id.delivery_amount > 0: purchase_price += (po_line.delivery_amt_line / po_line.product_qty) * line.qty_po + if line.purchase_order_id.delivery_amt > 0: + purchase_price += line.purchase_order_id.delivery_amt real_item_margin = sales_price - purchase_price sum_margin += real_item_margin diff --git a/indoteknik_custom/models/purchase_order_sales_match.py b/indoteknik_custom/models/purchase_order_sales_match.py index 78581409..d1d929d3 100644 --- a/indoteknik_custom/models/purchase_order_sales_match.py +++ b/indoteknik_custom/models/purchase_order_sales_match.py @@ -20,7 +20,17 @@ class PurchaseOrderSalesMatch(models.Model): product_id = fields.Many2one('product.product', string='Product') qty_so = fields.Float(string='Qty SO') qty_po = fields.Float(string='Qty PO') - margin_so = fields.Float(string='Margin SO') + margin_so = fields.Float(string='Margin SO') + 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') + + def _compute_delivery_amt(self): + for line in self: + percent_margin = line.margin_item / line.purchase_order_id.total_margin_match \ + if line.purchase_order_id.total_margin_match else 0 + line.delivery_amt = line.purchase_order_id.delivery_amt * percent_margin + line.margin_deduct = line.margin_item - line.delivery_amt @api.onchange('sale_id') def onchange_sale_id(self): diff --git a/indoteknik_custom/models/purchase_pricelist.py b/indoteknik_custom/models/purchase_pricelist.py index 68fb796e..e5b35d7f 100755 --- a/indoteknik_custom/models/purchase_pricelist.py +++ b/indoteknik_custom/models/purchase_pricelist.py @@ -23,6 +23,13 @@ class PurchasePricelist(models.Model): brand_id = fields.Many2one('x_manufactures', string='Brand') count_brand_vendor = fields.Integer(string='Count Brand Vendor') is_winner = fields.Boolean(string='Winner', default=False, help='Pemenang yang direkomendasikan oleh Merchandise') + + def sync_pricelist_tier(self): + for rec in self: + promotion_product = self.env['promotion.product'].search([('product_id', '=', rec.product_id.id)]) + + for promotion in promotion_product: + promotion.program_line_id.get_price_tier(promotion.product_id, promotion.qty) @api.depends('product_id', 'vendor_id') def _compute_name(self): @@ -111,5 +118,6 @@ class PurchasePricelist(models.Model): tier_prod_pricelist = self.env['product.pricelist.item'].search(product_domain + [('pricelist_id', '=', tier_pricelist.id)], limit=1) tier_prod_pricelist.price_discount = tier_perc + rec.sync_pricelist_tier() rec.product_id.product_tmpl_id._create_solr_queue('_sync_price_to_solr')
\ No newline at end of file diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index ac126337..2846c14b 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -18,7 +18,8 @@ class ResPartner(models.Model): ('pkp', 'PKP'), ('nonpkp', 'Non PKP') ]) - sppkp = fields.Char(string="SPPKP") + sppkp = fields.Char(string="SPPKP", tracking=3) + npwp = fields.Char(string="NPWP", tracking=3) counter = fields.Integer(string="Counter", default=0) leadtime = fields.Integer(string="Leadtime", default=0) digital_invoice_tax = fields.Boolean(string="Digital Invoice & Faktur Pajak") @@ -58,20 +59,98 @@ class ResPartner(models.Model): default=_default_payment_term ) + @api.depends("street", "street2", "city", "state_id", "country_id", "blok", "nomor", "rt", "rw", "kelurahan_id", + "kecamatan_id") + def _alamat_lengkap_text(self): + for partner in self: + if partner.company_type == 'person' and not partner.parent_id: + partner.alamat_lengkap_text = partner.street + # if partner.company_type == 'person' and partner.parent_id: + # partner.alamat_lengkap_text = partner.parent_id.alamat_lengkap_text + + + alamat_lengkap_text = fields.Text(string="Alamat Lengkap", required=False , tracking=3) + def write(self, vals): res = super(ResPartner, self).write(vals) + # + # # if 'property_payment_term_id' in vals: + # # if not self.env.user.is_accounting and vals['property_payment_term_id'] != 26: + # # raise UserError('Hanya Finance Accounting yang dapat merubah payment term') + # + # # group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').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('You name it') + # + return res - # if 'property_payment_term_id' in vals: - # if not self.env.user.is_accounting and vals['property_payment_term_id'] != 26: - # raise UserError('Hanya Finance Accounting yang dapat merubah payment term') - - # group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').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('You name it') + def write(self, vals): + # Fungsi rekursif untuk meng-update semua child, termasuk child dari child + def update_children_recursively(partner, vals_for_child): + # Lakukan update pada partner saat ini hanya dengan field yang diizinkan + partner.write(vals_for_child) + + # Untuk setiap child dari partner ini, update juga child-nya + for child in partner.child_ids: + update_children_recursively(child, vals_for_child) + + # Jika self tidak memiliki parent_id, artinya self adalah parent + if not self.parent_id: + # Ambil semua child dari parent ini + children = self.child_ids + + # Perbarui vals dengan nilai dari parent jika tidak ada dalam vals + vals['customer_type'] = vals.get('customer_type', self.customer_type) + vals['nama_wajib_pajak'] = vals.get('nama_wajib_pajak', self.nama_wajib_pajak) + vals['npwp'] = vals.get('npwp', self.npwp) + vals['sppkp'] = vals.get('sppkp', self.sppkp) + vals['alamat_lengkap_text'] = vals.get('alamat_lengkap_text', self.alamat_lengkap_text) + vals['industry_id'] = vals.get('industry_id', self.industry_id.id if self.industry_id else None) + vals['company_type_id'] = vals.get('company_type_id', + self.company_type_id.id if self.company_type_id else None) + + # Simpan hanya field yang perlu di-update pada child + vals_for_child = { + 'customer_type': vals.get('customer_type'), + 'nama_wajib_pajak': vals.get('nama_wajib_pajak'), + 'npwp': vals.get('npwp'), + 'sppkp': vals.get('sppkp'), + 'alamat_lengkap_text': vals.get('alamat_lengkap_text'), + 'industry_id': vals.get('industry_id'), + 'company_type_id': vals.get('company_type_id') + } + + # Lakukan update pada semua child secara rekursif + for child in children: + update_children_recursively(child, vals_for_child) + + # Lakukan write untuk parent dengan vals asli + res = super(ResPartner, self).write(vals) return res + # if self.company_type == 'person' and not partner.parent_id: + # if self.parent_id: + # parent = self.parent_id + # vals['industry_id'] = parent.industry_id.id + # vals['company_type_id'] = parent.company_type_id.id + # + # res = super(ResPartner, self).write(vals) + # return res + + @api.depends('company_type', 'parent_id', 'npwp', 'sppkp', 'nama_wajib_pajak','alamat_lengkap_text', 'industry_id', 'company_type_id') + def _related_fields(self): + for partner in self: + if partner.company_type == 'person' and partner.parent_id: + partner.customer_type = partner.parent_id.customer_type + partner.npwp = partner.parent_id.npwp + partner.sppkp = partner.parent_id.sppkp + partner.nama_wajib_pajak = partner.parent_id.nama_wajib_pajak + partner.alamat_lengkap_text = partner.parent_id.alamat_lengkap_text + partner.industry_id = partner.parent_id.industry_id.id + partner.company_type_id = partner.parent_id.company_type_id.id + @api.constrains('property_payment_term_id') def updated_by_payment_term(self): for rec in self: diff --git a/indoteknik_custom/models/res_users.py b/indoteknik_custom/models/res_users.py index 33f64ce3..5e16aad1 100755 --- a/indoteknik_custom/models/res_users.py +++ b/indoteknik_custom/models/res_users.py @@ -11,6 +11,8 @@ class ResUsers(models.Model): activation_token = fields.Char(string="Activation Token") otp_code = fields.Char(string='OTP Code') otp_create_date = fields.Datetime(string='OTP Create Date') + payment_terms_id = fields.Many2one('account.payment.term', related='partner_id.property_payment_term_id', string='Payment Terms') + def _generate_otp(self): for user in self: @@ -28,7 +30,22 @@ class ResUsers(models.Model): user._generate_otp() user._generate_activation_token() template.send_mail(user.id, force_send=True) - + + def send_company_request_mail(self): + template = self.env.ref('indoteknik_custom.mail_template_res_user_company_request') + for user in self: + template.send_mail(user.id, force_send=True) + + def send_company_request_approve_mail(self): + template = self.env.ref('indoteknik_custom.mail_template_res_user_company_request_approve') + for user in self: + template.send_mail(user.id, force_send=True) + + def send_company_request_reject_mail(self): + template = self.env.ref('indoteknik_custom.mail_template_res_user_company_request_reject') + for user in self: + template.send_mail(user.id, force_send=True) + def get_activation_token_url(self): base_url = self.env['ir.config_parameter'].get_param('site.base.url') return f'{base_url}/register?activation=token&token={self.activation_token}' diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 710e99de..8f48e898 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -38,14 +38,14 @@ class SaleOrder(models.Model): have_outstanding_po = fields.Boolean('Have Outstanding PO', compute='_have_outstanding_po') purchase_ids = fields.Many2many('purchase.order', string='Purchases', compute='_get_purchases') real_shipping_id = fields.Many2one( - 'res.partner', string='Real Delivery Address', readonly=True, required=True, + 'res.partner', string='Real Delivery Address', readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)], 'sale': [('readonly', False)]}, domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", - help="Dipakai untuk alamat tempel") + help="Dipakai untuk alamat tempel", tracking=True) real_invoice_id = fields.Many2one( 'res.partner', string='Delivery Invoice Address', required=True, domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", - help="Dipakai untuk alamat tempel") + help="Dipakai untuk alamat tempel", tracking=True) fee_third_party = fields.Float('Fee Pihak Ketiga') so_status = fields.Selection([ ('terproses', 'Terproses'), @@ -88,6 +88,8 @@ class SaleOrder(models.Model): voucher_id = fields.Many2one(comodel_name='voucher', string='Voucher', copy=False) applied_voucher_id = fields.Many2one(comodel_name='voucher', string='Applied Voucher', copy=False) amount_voucher_disc = fields.Float(string='Voucher Discount') + 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) email = fields.Char(string='Email') @@ -109,6 +111,159 @@ class SaleOrder(models.Model): date_driver_departure = fields.Datetime(string='Departure Date', compute='_compute_date_kirim', copy=False) note_website = fields.Char(string="Note Website") use_button = fields.Boolean(string='Using Calculate Selling Price', copy=False) + unreserve_id = fields.Many2one('stock.picking', 'Unreserve Picking') + voucher_shipping_id = fields.Many2one(comodel_name='voucher', string='Voucher Shipping', copy=False) + margin_after_delivery_purchase = fields.Float(string='Margin After Delivery Purchase', compute='_compute_margin_after_delivery_purchase') + percent_margin_after_delivery_purchase = fields.Float(string='% Margin After Delivery Purchase', compute='_compute_margin_after_delivery_purchase') + purchase_delivery_amt = fields.Float(string='Purchase Delivery Amount', compute='_compute_purchase_delivery_amount') + type_promotion = fields.Char(string='Type Promotion', compute='_compute_type_promotion') + partner_invoice_id = fields.Many2one( + 'res.partner', string='Invoice Address', + readonly=True, required=True, + states={'draft': [('readonly', False)], 'sent': [('readonly', False)], 'sale': [('readonly', False)]}, + domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", + tracking=True, # Menambahkan tracking=True + ) + partner_shipping_id = fields.Many2one( + 'res.partner', string='Delivery Address', readonly=True, required=True, + states={'draft': [('readonly', False)], 'sent': [('readonly', False)], 'sale': [('readonly', False)]}, + domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", tracking=True) + + payment_term_id = fields.Many2one( + 'account.payment.term', string='Payment Terms', check_company=True, # Unrequired company + domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", tracking=True) + + def action_estimate_shipping(self): + total_weight = 0 + missing_weight_products = [] + + for line in self.order_line: + if line.weight: + total_weight += line.weight + else: + missing_weight_products.append(line.product_id.name) + + 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_city_name = self.real_shipping_id.kota_id.name + + origin_city_id = self._get_city_id_by_name(origin_city_name) + destination_city_id = self._get_city_id_by_name(destination_city_name) + + if not origin_city_id or not destination_city_id: + raise UserError("Gagal mendapatkan ID kota asal atau tujuan.") + + result = self._call_rajaongkir_api(total_weight, origin_city_id, destination_city_id) + if result: + estimated_cost = result['rajaongkir']['results'][0]['costs'][0]['cost'][0]['value'] + self.delivery_amt = estimated_cost + self.message_post(body=f"Estimasi Ongkos Kirim: {estimated_cost}") + else: + raise UserError("Gagal mendapatkan estimasi ongkir.") + + def _call_rajaongkir_api(self, total_weight, origin_city_id, destination_city_id): + url = 'https://pro.rajaongkir.com/api/cost' + headers = { + 'key': '7ac9883688da043b50cc32f0e3070bb6', + } + courier = self.carrier_id.name.lower() + + data = { + 'origin': int(origin_city_id), + 'originType': 'city', + 'destination': int(destination_city_id), + 'destinationType': 'city', + 'weight': int(total_weight * 1000), + 'courier': courier, + } + + response = requests.post(url, headers=headers, data=data) + if response.status_code == 200: + return response.json() + return None + + def _normalize_city_name(self, city_name): + # Ubah nama kota menjadi huruf kecil + city_name = city_name.lower() + + # Hilangkan prefiks "kabupaten" atau "kota" jika ada + if city_name.startswith('kabupaten'): + city_name = city_name.replace('kabupaten', '').strip() + elif city_name.startswith('kota'): + city_name = city_name.replace('kota', '').strip() + + # Hilangkan spasi yang berlebihan + city_name = " ".join(city_name.split()) + + return city_name + + def _get_city_id_by_name(self, city_name): + url = 'https://pro.rajaongkir.com/api/city' + headers = { + 'key': '7ac9883688da043b50cc32f0e3070bb6', + } + + # Normalisasi nama kota sebelum melakukan pencarian + normalized_city_name = self._normalize_city_name(city_name) + + response = requests.get(url, headers=headers) + if response.status_code == 200: + city_data = response.json() + for city in city_data['rajaongkir']['results']: + if city['city_name'].lower() == normalized_city_name: + return city['city_id'] + return None + + def _get_subdistrict_id_by_name(self, city_id, subdistrict_name): + url = f'https://pro.rajaongkir.com/api/subdistrict?city={city_id}' + headers = { + 'key': '7ac9883688da043b50cc32f0e3070bb6', + } + + response = requests.get(url, headers=headers) + if response.status_code == 200: + subdistrict_data = response.json() + for subdistrict in subdistrict_data['rajaongkir']['results']: + if subdistrict['subdistrict_name'].lower() == subdistrict_name.lower(): + return subdistrict['subdistrict_id'] + return None + + + def _compute_type_promotion(self): + for rec in self: + promotion_types = [] + for promotion in rec.order_promotion_ids: + for line_program in promotion.program_line_id: + if line_program.promotion_type: + promotion_types.append(dict(line_program._fields['promotion_type'].selection).get(line_program.promotion_type)) + + rec.type_promotion = ', '.join(sorted(set(promotion_types))) + + def _compute_purchase_delivery_amount(self): + for order in self: + match = self.env['purchase.order.sales.match'] + result2 = match.search([ + ('sale_id.id', '=', order.id) + ]) + delivery_amt = 0 + for res in result2: + delivery_amt = res.delivery_amt + order.purchase_delivery_amt = delivery_amt + + def _compute_margin_after_delivery_purchase(self): + for order in self: + order.margin_after_delivery_purchase = order.total_margin - order.purchase_delivery_amt + if order.amount_untaxed == 0: + order.percent_margin_after_delivery_purchase = 0 + continue + if order.shipping_cost_covered == 'indoteknik': + 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) def _compute_date_kirim(self): for rec in self: @@ -389,10 +544,22 @@ 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.onchange('partner_shipping_id') - def onchange_partner_shipping(self): - self.real_shipping_id = self.partner_shipping_id - self.real_invoice_id = self.partner_invoice_id + + def check_data_real_delivery_address(self): + real_delivery_address = self.real_shipping_id + + if not real_delivery_address.state_id: + raise UserError('State Real Delivery Address harus diisi') + if not real_delivery_address.zip: + raise UserError('Zip code Real Delivery Address harus diisi') + if not real_delivery_address.mobile: + raise UserError('Mobile Real Delivery Address harus diisi') + if not real_delivery_address.phone: + raise UserError('Phone Real Delivery Address harus diisi') + if not real_delivery_address.kecamatan_id: + raise UserError('Kecamatan Real Delivery Address harus diisi') + if not real_delivery_address.kelurahan_id: + raise UserError('Kelurahan Real Delivery Address harus diisi') @api.onchange('partner_id') def onchange_partner_contact(self): @@ -630,6 +797,12 @@ class SaleOrder(models.Model): if not order.client_order_ref and order.create_date > datetime(2024, 6, 27): raise UserError("Customer Reference kosong, di isi dengan NO PO jika PO tidak ada mohon ditulis Tanpa PO") + + if not order.commitment_date and order.create_date > datetime(2024, 9, 12): + raise UserError("Expected Delivery Date kosong, wajib diisi") + + if not order.real_shipping_id: + UserError('Real Delivery Address harus di isi') if order.validate_partner_invoice_due(): return self._create_notification_action('Notification', 'Terdapat invoice yang telah melewati batas waktu, mohon perbarui pada dokumen Due Extension') @@ -743,7 +916,7 @@ 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)) * 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)) * 100, 2) @api.onchange('sales_tax_id') @@ -780,6 +953,28 @@ class SaleOrder(models.Model): self.apply_voucher() + def action_apply_voucher_shipping(self): + for line in self.order_line: + if line.order_promotion_id: + raise UserError('Voucher tidak dapat digabung dengan promotion program') + + voucher = self.voucher_shipping_id + if voucher.limit > 0 and voucher.count_order >= voucher.limit: + raise UserError('Voucher tidak dapat digunakan karena sudah habis digunakan') + + partner_voucher_orders = [] + for order in voucher.order_ids: + if order.partner_id.id == self.partner_id.id: + partner_voucher_orders.append(order) + + if voucher.limit_user > 0 and len(partner_voucher_orders) >= voucher.limit_user: + raise UserError('Voucher tidak dapat digunakan karena Customer ini sudah menghabiskan kuota voucher') + + if self.pricelist_id.id in [x.id for x in voucher.excl_pricelist_ids]: + raise UserError('Voucher tidak dapat digunakan karena pricelist ini tidak berlaku pada voucher') + + self.apply_voucher_shipping() + def apply_voucher(self): order_line = [] for line in self.order_line: @@ -821,6 +1016,29 @@ class SaleOrder(models.Model): self.amount_voucher_disc = voucher['discount']['all'] self.applied_voucher_id = self.voucher_id + def apply_voucher_shipping(self): + for order in self: + delivery_amt = order.delivery_amt + voucher = order.voucher_shipping_id + + if voucher: + max_discount_amount = voucher.discount_amount + voucher_type = voucher.discount_type + + if voucher_type == 'fixed_price': + discount = max_discount_amount + elif voucher_type == 'percentage': + discount = delivery_amt * (max_discount_amount / 100) + + delivery_amt -= discount + + delivery_amt = max(delivery_amt, 0) + + order.delivery_amt = delivery_amt + + order.amount_voucher_shipping_disc = discount + order.applied_voucher_shipping_id = order.voucher_id.id + def cancel_voucher(self): self.applied_voucher_id = False self.amount_voucher_disc = 0 @@ -829,6 +1047,11 @@ class SaleOrder(models.Model): line.discount = line.initial_discount line.initial_discount = False + def cancel_voucher_shipping(self): + self.delivery_amt + self.amount_voucher_shipping_disc + self.applied_voucher_shipping_id = False + self.amount_voucher_shipping_disc = 0 + def action_web_approve(self): if self.env.uid != self.partner_id.user_id.id: raise UserError('You are not authorized to approve this order. Only %s can approve this order.' % self.partner_id.user_id.name) @@ -942,3 +1165,38 @@ class SaleOrder(models.Model): order_line.tax_id = tax_id order_line.discount = discount order_line.order_id.use_button = True + + @api.model + def create(self, vals): + # Ensure partner details are updated when a sale order is created + order = super(SaleOrder, self).create(vals) + order._update_partner_details() + return order + + def write(self, vals): + # Call the super method to handle the write operation + res = super(SaleOrder, self).write(vals) + + # 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 + + def _update_partner_details(self): + for order in self: + partner = order.partner_id.parent_id or order.partner_id + if partner: + # Update partner details + partner.sppkp = order.sppkp + partner.npwp = order.npwp + partner.email = order.email + partner.customer_type = order.customer_type + + # Save changes to the partner record + partner.write({ + 'sppkp': partner.sppkp, + 'npwp': partner.npwp, + 'email': partner.email, + 'customer_type': partner.customer_type, + })
\ 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 a64a744c..d1dcd0af 100644 --- a/indoteknik_custom/models/sale_order_line.py +++ b/indoteknik_custom/models/sale_order_line.py @@ -31,6 +31,7 @@ class SaleOrderLine(models.Model): qty_reserved = fields.Float(string='Qty Reserved', compute='_compute_qty_reserved') reserved_from = fields.Char(string='Reserved From', copy=False) item_percent_margin_without_deduction = fields.Float('%Margin', compute='_compute_item_margin_without_deduction') + weight = fields.Float(string='Weight') @api.constrains('note_procurement') def note_procurement_to_apo(self): @@ -257,6 +258,7 @@ class SaleOrderLine(models.Model): ('(' + attribute_values_str + ')' if attribute_values_str else '') + ' ' + \ (line.product_id.short_spesification if line.product_id.short_spesification else '') line.name = line_name + line.weight = line.product_id.weight def compute_delivery_amt_line(self): for line in self: @@ -348,7 +350,7 @@ class SaleOrderLine(models.Model): def validate_line(self): for line in self: - if line.product_id.id in [385544, 224484]: + if line.product_id.id in [385544, 224484, 417724]: raise UserError('Produk Sementara Tidak Bisa Di Confirm atau Ask Approval') if not line.product_id or line.product_id.type == 'service': continue diff --git a/indoteknik_custom/models/solr/__init__.py b/indoteknik_custom/models/solr/__init__.py index 606c0035..dafd5a1e 100644 --- a/indoteknik_custom/models/solr/__init__.py +++ b/indoteknik_custom/models/solr/__init__.py @@ -10,4 +10,5 @@ from . import x_banner_banner from . import product_public_category from . import x_banner_category from . import promotion_program -from . import promotion_program_line
\ No newline at end of file +from . import promotion_program_line +from . import website_categories_management
\ No newline at end of file diff --git a/indoteknik_custom/models/solr/product_template.py b/indoteknik_custom/models/solr/product_template.py index d8dec47c..1eb6f31b 100644 --- a/indoteknik_custom/models/solr/product_template.py +++ b/indoteknik_custom/models/solr/product_template.py @@ -112,8 +112,8 @@ class ProductTemplate(models.Model): "description_clean_t": cleaned_desc or '', 'has_product_info_b': True, 'publish_b': not template.unpublished, - 'sni_b': template.unpublished, - 'tkdn_b': template.unpublished, + 'sni_b': template.sni, + 'tkdn_b': template.tkdn, "qty_sold_f": template.qty_sold, "is_in_bu_b": is_in_bu, "voucher_min_purchase_f" : voucher.min_purchase_amount or 0, diff --git a/indoteknik_custom/models/solr/promotion_program_line.py b/indoteknik_custom/models/solr/promotion_program_line.py index 3e3a2a28..64ad4209 100644 --- a/indoteknik_custom/models/solr/promotion_program_line.py +++ b/indoteknik_custom/models/solr/promotion_program_line.py @@ -53,6 +53,11 @@ class PromotionProgramLine(models.Model): 'package_limit_user_i': rec.package_limit_user, 'package_limit_trx_i': rec.package_limit_trx, 'price_f': rec.price, + 'price_tier_1_f': rec.price_tier_1, + 'price_tier_2_f': rec.price_tier_2, + 'price_tier_3_f': rec.price_tier_3, + 'price_tier_4_f': rec.price_tier_4, + 'price_tier_5_f': rec.price_tier_5, 'sequence_i': sequence_value, 'product_ids': [x.product_id.id for x in rec.product_ids], 'products_s': json.dumps(products), diff --git a/indoteknik_custom/models/solr/website_categories_management.py b/indoteknik_custom/models/solr/website_categories_management.py new file mode 100644 index 00000000..0a40a356 --- /dev/null +++ b/indoteknik_custom/models/solr/website_categories_management.py @@ -0,0 +1,114 @@ +from odoo import models, fields, api +from datetime import datetime +import json + +class WebsiteCategoriesHomepage(models.Model): + _inherit = 'website.categories.management' + + last_update_solr = fields.Datetime('Last Update Solr') + + def solr(self): + """Returns the Solr connection object.""" + return self.env['apache.solr'].connect('category_management') + + def update_last_update_solr(self): + """Updates the last sync time for the record.""" + self.last_update_solr = datetime.utcnow() + + def _create_solr_queue(self, function_name): + """Creates unique Solr queue for each record.""" + for rec in self: + self.env['apache.solr.queue'].create_unique({ + 'res_model': self._name, + 'res_id': rec.id, + 'function_name': function_name + }) + + @api.constrains('status') + def _create_solr_queue_sync_status(self): + """Triggers Solr sync when the status changes.""" + self._create_solr_queue('_sync_status_category_homepage_solr') + + @api.constrains('category_id', 'category_id2', 'sequence') + def _create_solr_queue_sync_category_homepage(self): + """Triggers Solr sync when categories or sequence change.""" + self._create_solr_queue('_sync_category_management_to_solr') + + def action_sync_to_solr(self): + """Manual action to sync selected categories to Solr.""" + category_ids = self.env.context.get('active_ids', []) + categories = self.search([('id', 'in', category_ids)]) + categories._create_solr_queue('_sync_category_management_to_solr') + + def unlink(self): + """Overrides unlink method to remove records from Solr.""" + for rec in self: + self.solr().delete(rec.id) + self.solr().optimize() + self.solr().commit() + return super(WebsiteCategoriesHomepage, self).unlink() + + def _sync_status_category_homepage_solr(self): + """Syncs status to Solr or deletes if not active.""" + for rec in self: + if rec.status == 'tayang': + rec._sync_category_management_to_solr() + else: + rec.unlink() + + def _sync_category_management_to_solr(self): + """Syncs categories (Level 1, 2, and 3) to Solr.""" + solr_model = self.env['apache.solr'] + + for category in self: + if category.status != 'tayang': + continue + + # Prepare Level 1 document + document = { + 'id': category.id, + 'sequence_i': category.sequence or '', + 'category_id_i': category.category_id.id, + 'name_s': category.category_id.name, + 'numFound_i': len(category.category_id.product_tmpl_ids), + 'image_s': self.env['ir.attachment'].api_image( + 'product.public.category', 'image_1920', category.category_id.id + ), + 'categories': [] + } + + # Prepare Level 2 documents + for category_level_2 in category.line_ids.mapped('category_id2'): + level_2_doc = { + 'id_level_2': category_level_2.id, + 'name': category_level_2.name, + 'numFound': len(category_level_2.product_tmpl_ids), + 'image': self.env['ir.attachment'].api_image( + 'product.public.category', 'image_1920', category_level_2.id + ), + 'child_frontend_id_i': [] + } + + # Prepare Level 3 documents + for category_level_3 in category_level_2.child_frontend_id2: + level_3_doc = { + 'id_level_3': category_level_3.id, + 'name': category_level_3.name, + 'numFound': len(category_level_3.product_tmpl_ids), + 'image': self.env['ir.attachment'].api_image( + 'product.public.category', 'image_1920', category_level_3.id + ), + } + level_2_doc['child_frontend_id_i'].append(json.dumps(level_3_doc)) + + # Add Level 2 document to Level 1 + document['categories'].append(json.dumps(level_2_doc)) + + # Sync document with Solr + self.solr().add([document]) + category.update_last_update_solr() + + # Commit and optimize Solr changes + self.solr().commit() + self.solr().optimize() + diff --git a/indoteknik_custom/models/stock_move.py b/indoteknik_custom/models/stock_move.py index fe46bf65..ac2e3cc0 100644 --- a/indoteknik_custom/models/stock_move.py +++ b/indoteknik_custom/models/stock_move.py @@ -1,5 +1,6 @@ from odoo import fields, models, api - +from odoo.tools.misc import format_date, OrderedSet +from odoo.exceptions import UserError class StockMove(models.Model): _inherit = 'stock.move' @@ -7,6 +8,47 @@ class StockMove(models.Model): line_no = fields.Integer('No', default=0) sale_id = fields.Many2one('sale.order', string='SO') + def _do_unreserve(self, product=None, quantity=False): + moves_to_unreserve = OrderedSet() + for move in self: + if move.state == 'cancel' or (move.state == 'done' and move.scrapped): + continue + elif move.state == 'done': + raise UserError("You cannot unreserve a stock move that has been set to 'Done'.") + + if product and move.product_id != product: + continue # Skip moves that don't match the specified product + moves_to_unreserve.add(move.id) + + moves_to_unreserve = self.env['stock.move'].browse(moves_to_unreserve) + + ml_to_update, ml_to_unlink = OrderedSet(), OrderedSet() + moves_not_to_recompute = OrderedSet() + + for ml in moves_to_unreserve.move_line_ids: + if product and ml.product_id != product: + continue # Only affect the specified product + + if quantity and quantity > 0: + # Only reduce by the specified quantity if it is greater than zero + ml_to_update.add(ml.id) + remaining_qty = ml.product_uom_qty - quantity + ml.write({'product_uom_qty': remaining_qty if remaining_qty > 0 else 0}) + quantity = 0 # Set to zero to prevent further unreserving in the same loop + elif ml.qty_done: + ml_to_update.add(ml.id) + else: + ml_to_unlink.add(ml.id) + moves_not_to_recompute.add(ml.move_id.id) + + ml_to_update, ml_to_unlink = self.env['stock.move.line'].browse(ml_to_update), self.env['stock.move.line'].browse(ml_to_unlink) + moves_not_to_recompute = self.env['stock.move'].browse(moves_not_to_recompute) + + ml_to_unlink.unlink() + (moves_to_unreserve - moves_not_to_recompute)._recompute_state() + return True + + def _prepare_account_move_line_from_mr(self, po_line, qty, move=False): po_line.ensure_one() aml_currency = move and move.currency_id or po_line.currency_id diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index d16d508e..66a326ff 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -125,12 +125,27 @@ class StockPicking(models.Model): raise UserError('Hanya Logistic yang bisa mengubah shipping method') def do_unreserve(self): + if not self._context.get('darimana') == 'sale.order': + self.sale_id.unreserve_id = self.id + return self._create_approval_notification('Logistic') + res = super(StockPicking, self).do_unreserve() - if not self.env.user.is_purchasing_manager: - raise UserError('Hanya Purchasing Manager yang bisa Unreserve') current_time = datetime.datetime.utcnow() self.date_unreserve = current_time + return res + + 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'} }, + } def _compute_shipping_status(self): for rec in self: @@ -373,6 +388,9 @@ class StockPicking(models.Model): 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") if self.group_id.sale_id: if self.group_id.sale_id.payment_link_midtrans: diff --git a/indoteknik_custom/models/user_company_request.py b/indoteknik_custom/models/user_company_request.py index 2467261a..d540b0f6 100644 --- a/indoteknik_custom/models/user_company_request.py +++ b/indoteknik_custom/models/user_company_request.py @@ -1,6 +1,6 @@ from odoo import models, fields from odoo.exceptions import UserError - +from odoo.http import request class UserCompanyRequest(models.Model): _name = 'user.company.request' @@ -15,6 +15,8 @@ class UserCompanyRequest(models.Model): ], string='Approval') def write(self, vals): + user = self.get_user_by_email(self.user_id.email) + user.parent_name = self.user_input is_approve = vals.get('is_approve') if self.is_approve and is_approve: raise UserError('Tidak dapat mengubah approval yang sudah diisi') @@ -22,10 +24,28 @@ class UserCompanyRequest(models.Model): if not self.is_approve and is_approve: if is_approve == 'approved': self.user_id.parent_id = self.user_company_id.id + self.user_id.customer_type = self.user_company_id.customer_type + self.user_id.npwp = self.user_company_id.npwp + self.user_id.sppkp = self.user_company_id.sppkp + self.user_id.nama_wajib_pajak = self.user_company_id.nama_wajib_pajak + self.user_id.alamat_lengkap_text = self.user_company_id.alamat_lengkap_text + self.user_id.industry_id = self.user_company_id.industry_id.id + self.user_id.company_type_id = self.user_company_id.company_type_id.id + self.user_id.user_id = self.user_company_id.user_id + self.user_id.property_account_receivable_id = self.user_company_id.property_account_receivable_id + self.user_id.property_account_payable_id = self.user_company_id.property_account_payable_id + self.user_company_id.active = True + user.send_company_request_approve_mail() else: new_company = self.env['res.partner'].create({ 'name': self.user_input }) - self.user_id.parent_id = new_company.id + # self.user_id.parent_id = new_company.id + user.send_company_request_reject_mail() return super(UserCompanyRequest, self).write(vals) -
\ No newline at end of file + + 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_custom/models/website_categories_management.py b/indoteknik_custom/models/website_categories_management.py index 208b07a2..e430ef5f 100644 --- a/indoteknik_custom/models/website_categories_management.py +++ b/indoteknik_custom/models/website_categories_management.py @@ -6,8 +6,8 @@ class WebsiteCategoriesManagement(models.Model): _rec_name = 'category_id' category_id = fields.Many2one('product.public.category', string='Category Level 1', help='table ecommerce category', domain=lambda self: self._get_default_category_domain()) - category_id2 = fields.Many2many(comodel_name='product.public.category', relation='website_categories_category_id2_rel',column1='website_categories_homepage_id', column2='product_public_category_id', string='Category Level 2', copy=False) sequence = fields.Integer(string='Sequence') + line_ids = fields.One2many('website.categories.management.line', 'management_id', string='Category Level 2 Lines', auto_join=True) status = fields.Selection([ ('tayang', 'Tayang'), ('tidak_tayang', 'Tidak Tayang') @@ -17,11 +17,11 @@ class WebsiteCategoriesManagement(models.Model): def _onchange_category_id(self): domain = {} if self.category_id != self._origin.category_id: # Check if the category_id has changed - self.category_id2 = [(5, 0, 0)] # Clear the category_id2 field if category_id has changed + self.line_ids = [(5, 0, 0)] # Clear the lines if category_id has changed if self.category_id: - domain['category_id2'] = [('parent_frontend_id', '=', self.category_id.id)] + domain['line_ids'] = [('parent_frontend_id', '=', self.category_id.id)] else: - domain['category_id2'] = [] + domain['line_ids'] = [] return {'domain': domain} @@ -42,24 +42,30 @@ class WebsiteCategoriesManagement(models.Model): def _check_category_consistency(self): for record in self: - category_ids = record.category_id2.ids - for category in record.category_id2: - for child_category in category.child_frontend_id2: - if child_category.parent_frontend_id.id not in category_ids: + category_level2_ids = record.line_ids.mapped('category_id2.id') # Get all Category Level 2 IDs + for line in record.line_ids: + for category_level3 in line.category_id3_ids: # Loop through selected Category Level 3 + if category_level3.parent_frontend_id.id not in category_level2_ids: raise ValidationError( - f"Category Level 3 {child_category.name} bukan bagian dari category Level 2 {category.name}") + f"Category Level 3 '{category_level3.name}' bukan bagian dari Category Level 2 '{line.category_id2.name}'") def unlink(self): - for record in self.category_id2: - if record.id: + for record in self.line_ids: + if record.category_id2: related_categories = self.env['product.public.category'].search([ - ('id', 'in', record.ids) + ('id', '=', record.category_id2.id) ]) for category in related_categories: - for category3 in record.child_frontend_id2.ids: - if category3 in category.child_frontend_id2.ids: + # Iterate through the Category Level 3 related to the current Category Level 2 + for category3 in record.category_id3_ids: + # If Category Level 3 is linked to Category Level 2, remove the link + if category3.id in category.child_frontend_id2.ids: category.write({ - 'child_frontend_id2': [(3, category3)] + 'child_frontend_id2': [(3, category3.id)] + # Remove the link between Category Level 2 and Category Level 3 }) + return super(WebsiteCategoriesManagement, self).unlink() + + diff --git a/indoteknik_custom/models/website_categories_management_line.py b/indoteknik_custom/models/website_categories_management_line.py new file mode 100644 index 00000000..2f97ddfa --- /dev/null +++ b/indoteknik_custom/models/website_categories_management_line.py @@ -0,0 +1,22 @@ +from odoo import fields, models, api +from odoo.exceptions import ValidationError + +class WebsiteCategoriesManagementLine(models.Model): + _name = 'website.categories.management.line' + _order = 'sequence' + + sequence = fields.Integer(string='Sequence') + management_id = fields.Many2one('website.categories.management', string='Management Reference', required=True, ondelete='cascade') + category_id2 = fields.Many2one('product.public.category', string='Category Level 2', required=True,) + category_id3_ids = fields.Many2many('product.public.category', string='Category Level 3') + + @api.onchange('category_id2') + def _onchange_category_id2(self): + """ Update domain for category_id3_ids based on category_id2 """ + if self.category_id2: + domain_category_id3_ids = [('parent_frontend_id', '=', self.category_id2.id)] + else: + domain_category_id3_ids = [] + + return {'domain': {'category_id3_ids': domain_category_id3_ids}} + diff --git a/indoteknik_custom/models/website_user_cart.py b/indoteknik_custom/models/website_user_cart.py index 0af22d47..169f4a6b 100644 --- a/indoteknik_custom/models/website_user_cart.py +++ b/indoteknik_custom/models/website_user_cart.py @@ -1,4 +1,4 @@ -from odoo import fields, models +from odoo import fields, models, api from datetime import datetime, timedelta class WebsiteUserCart(models.Model): @@ -16,6 +16,26 @@ class WebsiteUserCart(models.Model): ], 'Source', default='add_to_cart') user_other_carts = fields.One2many('website.user.cart', 'id', 'Other Products', compute='_compute_user_other_carts') 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') + + @api.depends('program_line_id') + def _compute_program_product_ids(self): + for record in self: + if record.program_line_id and record.program_line_id.product_ids: + product = record.program_line_id.product_ids[0] + record.program_product_id = product.product_id + record.product_id = product.product_id + else: + # Handle case where there are no product_ids + record.program_product_id = False + + def _compute_price(self): + for record in self: + record.price = record.get_price_website(record.product_id.id)['price'] + if record.program_line_id: + record.price = record.get_price_coret(record.program_line_id.id) def _compute_user_other_carts(self): for record in self: @@ -130,8 +150,7 @@ class WebsiteUserCart(models.Model): if voucher_shipping: voucher_shipping_info = voucher_shipping.apply(order_line) - discount_voucher_shipping = voucher_shipping_info['discount']['all'] - subtotal -= discount_voucher_shipping + discount_voucher_shipping = voucher_shipping_info['discount']['all'] tax = round(subtotal * 0.11) grand_total = subtotal + tax diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv index 5e7554a5..19e3bdca 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -23,6 +23,7 @@ access_website_brand_homepage,access.website.brand.homepage,model_website_brand_ access_website_categories_homepage,access.website.categories.homepage,model_website_categories_homepage,,1,1,1,1 access_website_categories_lob,access.website.categories.lob,model_website_categories_lob,,1,1,1,1 access_website_categories_management,access.website.categories.management,model_website_categories_management,,1,1,1,1 +access_website_categories_management_line,access.website.categories.management.line,model_website_categories_management_line,,1,1,1,1 access_sales_target,access.sales.target,model_sales_target,,1,1,1,1 access_purchase_outstanding,access.purchase.outstanding,model_purchase_outstanding,,1,1,1,1 access_sales_outstanding,access.sales.outstanding,model_sales_outstanding,,1,1,1,1 @@ -134,3 +135,6 @@ 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_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 diff --git a/indoteknik_custom/views/account_move.xml b/indoteknik_custom/views/account_move.xml index 93145fea..2863af57 100644 --- a/indoteknik_custom/views/account_move.xml +++ b/indoteknik_custom/views/account_move.xml @@ -85,6 +85,7 @@ </field> <field name="invoice_date_due" position="after"> <field name="new_due_date" optional="hide"/> + <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="mark_upload_efaktur" optional="hide" widget="badge" diff --git a/indoteknik_custom/views/account_move_views.xml b/indoteknik_custom/views/account_move_views.xml index 1c70cc7b..4acafb14 100644 --- a/indoteknik_custom/views/account_move_views.xml +++ b/indoteknik_custom/views/account_move_views.xml @@ -1,117 +1,157 @@ <?xml version="1.0" encoding="UTF-8"?> <odoo> - <record id="due_extension_tree" model="ir.ui.view"> - <field name="name">due.extension.tree</field> - <field name="model">due.extension</field> - <field name="arch" type="xml"> - <tree default_order="create_date desc" create="0"> - <field name="number"/> - <field name="partner_id"/> - <field name="day_extension"/> - <field name="description"/> - <field name="approval_status"/> - <field name="is_approve"/> - </tree> - </field> - </record> + <data> + <record id="due_extension_tree" model="ir.ui.view"> + <field name="name">due.extension.tree</field> + <field name="model">due.extension</field> + <field name="arch" type="xml"> + <tree default_order="create_date desc" create="0"> + <field name="number"/> + <field name="partner_id"/> + <field name="day_extension"/> + <field name="description"/> + <field name="approval_status"/> + <field name="is_approve"/> + </tree> + </field> + </record> - <record id="due_extension_line_tree" model="ir.ui.view"> - <field name="name">due.extension.line.tree</field> - <field name="model">due.extension.line</field> - <field name="arch" type="xml"> - <tree> - <field name="partner_id"/> - <field name="invoice_id"/> - <field name="date_invoice"/> - <field name="due_date"/> - <field name="day_to_due"/> - <field name="efaktur_id"/> - <field name="reference"/> - <field name="total_amt"/> - <field name="open_amt"/> - </tree> - </field> - </record> + <record id="due_extension_line_tree" model="ir.ui.view"> + <field name="name">due.extension.line.tree</field> + <field name="model">due.extension.line</field> + <field name="arch" type="xml"> + <tree> + <field name="partner_id"/> + <field name="invoice_id"/> + <field name="date_invoice"/> + <field name="due_date"/> + <field name="day_to_due"/> + <field name="efaktur_id"/> + <field name="reference"/> + <field name="total_amt"/> + <field name="open_amt"/> + </tree> + </field> + </record> - <record id="due_extension_form" model="ir.ui.view"> - <field name="name">due.extension.form</field> - <field name="model">due.extension</field> - <field name="arch" type="xml"> - <form create="false"> - <header> - <button name="approve_new_due" - string="Approve" - type="object" - /> - <button name="due_extension_approval" - string="Ask Approval" + <record id="due_extension_form" model="ir.ui.view"> + <field name="name">due.extension.form</field> + <field name="model">due.extension</field> + <field name="arch" type="xml"> + <form create="false"> + <header> + <button name="approve_new_due" + string="Approve" type="object" - /> - <button name="due_extension_cancel" - string="Cancel" - type="object" - /> - </header> - <sheet> - <group> + /> + <button name="due_extension_approval" + string="Ask Approval" + type="object" + /> + <button name="due_extension_cancel" + string="Cancel" + type="object" + /> + </header> + <sheet> <group> - <field name="partner_id" readonly="1"/> - <field name="day_extension" attrs="{'readonly': [('is_approve', '=', True)]}"/> + <group> + <field name="partner_id" readonly="1"/> + <field name="day_extension" attrs="{'readonly': [('is_approve', '=', True)]}"/> + </group> + <group> + <field name="is_approve" readonly="1"/> + <field name="order_id" readonly="1"/> + <field name="counter" readonly="1"/> + <field name="approval_status" readonly="1"/> + </group> </group> <group> - <field name="is_approve" readonly="1"/> - <field name="order_id" readonly="1"/> - <field name="counter" readonly="1"/> - <field name="approval_status" readonly="1"/> + <field name="description" attrs="{'readonly': [('approval_status', '=', 'approved')]}"/> </group> - </group> - <group> - <field name="description" attrs="{'readonly': [('approval_status', '=', 'approved')]}"/> - </group> - <notebook> - <page string="Invoices"> - <field name="due_line" attrs="{'readonly': [('is_approve', '=', True)]}"/> - </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> + <notebook> + <page string="Invoices"> + <field name="due_line" attrs="{'readonly': [('is_approve', '=', True)]}"/> + </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> - <record id="due_extension_view_search" model="ir.ui.view"> - <field name="name">due.extension.search.view</field> <!-- Made the name more descriptive --> - <field name="model">due.extension</field> - <field name="arch" type="xml"> - <search string="Search Due Extension"> - <field name="number"/> - <field name="partner_id"/> - <field name="invoice_id"/> - <field name="order_id"/> - </search> - </field> - </record> + <record id="due_extension_view_search" model="ir.ui.view"> + <field name="name">due.extension.search.view</field> <!-- Made the name more descriptive --> + <field name="model">due.extension</field> + <field name="arch" type="xml"> + <search string="Search Due Extension"> + <field name="number"/> + <field name="partner_id"/> + <field name="invoice_id"/> + <field name="order_id"/> + </search> + </field> + </record> - <record id="due_extension_action" model="ir.actions.act_window"> - <field name="name">Due Extension</field> - <field name="type">ir.actions.act_window</field> - <field name="res_model">due.extension</field> - <field name="view_mode">tree,form</field> - </record> + <record id="due_extension_action" model="ir.actions.act_window"> + <field name="name">Due Extension</field> + <field name="type">ir.actions.act_window</field> + <field name="res_model">due.extension</field> + <field name="view_mode">tree,form</field> + </record> - <menuitem - id="menu_due_extension" - name="Due Extension" - parent="sale.product_menu_catalog" - sequence="4" - action="due_extension_action" - /> - <menuitem id="menu_due_extension" name="Due Extension" - parent="account.menu_finance_receivables" - action="due_extension_action" - sequence="100" - /> + <menuitem + id="menu_due_extension" + name="Due Extension" + parent="sale.product_menu_catalog" + sequence="4" + action="due_extension_action" + /> + <menuitem id="menu_due_extension" name="Due Extension" + parent="account.menu_finance_receivables" + action="due_extension_action" + sequence="100" + /> + + <record id="mail_template_due_extension_approve" model="mail.template"> + <field name="name">Due Extension: Approve</field> + <field name="model_id" ref="indoteknik_custom.model_due_extension"/> + <field name="subject">Approval for Due Extension: DE ${object.name}</field> + <field name="email_from">"PT. Indoteknik Dotcom Gemilang" <noreply@indoteknik.com></field> + <field name="reply_to">anto@indoteknik.co.id</field> + <field name="email_to"> ${object.order_id.user_id.login | safe}</field> + <field name="body_html" type="html"> + <p>Dear Anto,</p> + + <p>We would like to inform you that the due extension for the following document has been approved:</p> + + <table border="0" cellpadding="5" cellspacing="0" width="15%"> + <tr> + <td><strong>DE Number:</strong></td> + <td>${object.number}</td> + </tr> + <tr> + <td><strong>SO Number:</strong></td> + <td>${object.order_id.name}</td> + </tr> + <tr> + <td><strong>New Due Date:</strong></td> + <td>${object.due_line[0].invoice_id.new_due_date}</td> + </tr> + <tr> + <td><strong>Extension (days):</strong></td> + <td>${object.day_extension}</td> + </tr> + </table> + + <p>If you have any further questions or need additional information, please feel free to contact us.</p> + + <p>Best regards,</p> + <p><strong>PT. Indoteknik Dotcom Gemilang</strong></p> + </field> + </record> + </data> </odoo> diff --git a/indoteknik_custom/views/approval_unreserve.xml b/indoteknik_custom/views/approval_unreserve.xml new file mode 100644 index 00000000..317b5276 --- /dev/null +++ b/indoteknik_custom/views/approval_unreserve.xml @@ -0,0 +1,81 @@ +<?xml version="1.0" encoding="UTF-8"?> +<odoo> + <record id="view_approval_unreserve_tree" model="ir.ui.view"> + <field name="name">approval.unreserve.tree</field> + <field name="model">approval.unreserve</field> + <field name="arch" type="xml"> + <tree string="Approval Unreserve"> + <field name="number"/> + <field name="request_date"/> + <field name="picking_id"/> + <field name="state"/> + <field name="user_id"/> + </tree> + </field> + </record> + + <record id="approval_unreserve_line_tree" model="ir.ui.view"> + <field name="name">approval.unreserve.line.tree</field> + <field name="model">approval.unreserve.line</field> + <field name="arch" type="xml"> + <tree editable="bottom"> + <field name="move_id"/> + <field name="product_id"/> + <field name="dest_picking_id"/> + <field name="sales_id"/> + <field name="unreserve_qty"/> + </tree> + </field> + </record> + + <record id="approval_unreserve_form" model="ir.ui.view"> + <field name="name">approval.unreserve.form</field> + <field name="model">approval.unreserve</field> + <field name="arch" type="xml"> + <form create="false"> + <header> + <button name="action_submit_for_approval" type="object" string="Submit for Approval" attrs="{'invisible': [('state', '!=', 'draft')]}"/> + <button name="action_approve" type="object" string="Approve" attrs="{'invisible': [('state', '!=', 'waiting_approval')]}"/> + <button name="action_reject" type="object" string="Reject" attrs="{'invisible': [('state', '!=', 'waiting_approval')]}"/> + </header> + <sheet> + <group> + <group> + <field name="number" readonly="1"/> + <field name="request_date" readonly="1"/> + <field name="picking_id"/> + <field name="user_id" readonly="1"/> + <field name="state" readonly="1"/> + <field name="approved_by" readonly="1"/> + <field name="reason"/> + </group> + </group> + <notebook> + <page string="Lines"> + <field name="approval_line"/> + </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> + + <record id="approval_unreserve_action" model="ir.actions.act_window"> + <field name="name">Approval Unreserve</field> + <field name="type">ir.actions.act_window</field> + <field name="res_model">approval.unreserve</field> + <field name="view_mode">tree,form</field> + </record> + + <menuitem + id="menu_approval_unreserve" + name="Approval Unreserve" + parent="sale.product_menu_catalog" + sequence="4" + action="approval_unreserve_action" + /> +</odoo> diff --git a/indoteknik_custom/views/bill_receipt.xml b/indoteknik_custom/views/bill_receipt.xml index 15d82e7b..02b28ddf 100644 --- a/indoteknik_custom/views/bill_receipt.xml +++ b/indoteknik_custom/views/bill_receipt.xml @@ -13,6 +13,7 @@ <field name="resi_tukar_faktur"/> <field name="date_terima_tukar_faktur"/> <field name="shipper_faktur_id"/> + <field name="grand_total"/> </tree> </field> </record> @@ -69,6 +70,7 @@ <field name="resi_tukar_faktur"/> <field name="date_terima_tukar_faktur"/> <field name="shipper_faktur_id"/> + <field name="grand_total"/> </group> </group> <notebook> diff --git a/indoteknik_custom/views/dunning_run.xml b/indoteknik_custom/views/dunning_run.xml index dd5bb120..522be8c9 100644 --- a/indoteknik_custom/views/dunning_run.xml +++ b/indoteknik_custom/views/dunning_run.xml @@ -13,6 +13,7 @@ <field name="resi_tukar_faktur"/> <field name="date_terima_tukar_faktur"/> <field name="shipper_faktur_id"/> + <field name="grand_total"/> </tree> </field> </record> @@ -71,6 +72,7 @@ <field name="resi_tukar_faktur"/> <field name="date_terima_tukar_faktur"/> <field name="shipper_faktur_id"/> + <field name="grand_total"/> </group> </group> <notebook> diff --git a/indoteknik_custom/views/ir_sequence.xml b/indoteknik_custom/views/ir_sequence.xml index b2768c71..dd501d8c 100644 --- a/indoteknik_custom/views/ir_sequence.xml +++ b/indoteknik_custom/views/ir_sequence.xml @@ -20,6 +20,16 @@ <field name="number_next">1</field> <field name="number_increment">1</field> </record> + + <record id="sequence_approval_unreserve" model="ir.sequence"> + <field name="name">Approval Unreserve</field> + <field name="code">approval.unreserve</field> + <field name="active">TRUE</field> + <field name="prefix">AU/%(year)s/</field> + <field name="padding">5</field> + <field name="number_next">1</field> + <field name="number_increment">1</field> + </record> <record id="sequence_logbook_sj" model="ir.sequence"> <field name="name">Logbook SJ</field> diff --git a/indoteknik_custom/views/product_template.xml b/indoteknik_custom/views/product_template.xml index b6155eea..a77b99de 100755 --- a/indoteknik_custom/views/product_template.xml +++ b/indoteknik_custom/views/product_template.xml @@ -22,7 +22,7 @@ <field name="last_update_solr" readonly="1" /> </field> <field name="public_categ_ids" position="attributes"> - <attribute name="required">1</attribute> + <attribute name="required">0</attribute> </field> <field name="public_categ_ids" position="attributes"> <attribute name="options">{'no_create': True}</attribute> diff --git a/indoteknik_custom/views/promotion/promotion_program_line.xml b/indoteknik_custom/views/promotion/promotion_program_line.xml index f3c2eea1..9cda67a8 100644 --- a/indoteknik_custom/views/promotion/promotion_program_line.xml +++ b/indoteknik_custom/views/promotion/promotion_program_line.xml @@ -32,6 +32,11 @@ <field name="package_limit_user" /> <field name="package_limit_trx" /> <field name="price" attrs="{'invisible': [('promotion_type', '=', 'special_price')]}" /> + <field name="price_tier_1"/> + <field name="price_tier_2" invisible="1"/> + <field name="price_tier_3" invisible="1"/> + <field name="price_tier_4" invisible="1"/> + <field name="price_tier_5" invisible="1"/> <field name="sequence"/> <field name="discount_type" attrs="{'invisible': [('promotion_type', '!=', 'special_price')]}" /> <field name="discount_amount" attrs="{'invisible': [('promotion_type', '!=', 'special_price')]}" /> diff --git a/indoteknik_custom/views/purchase_order.xml b/indoteknik_custom/views/purchase_order.xml index c301f3d3..f6e5a1a4 100755 --- a/indoteknik_custom/views/purchase_order.xml +++ b/indoteknik_custom/views/purchase_order.xml @@ -26,15 +26,21 @@ <button name="button_unlock" position="after"> <button name="delete_line" type="object" string="Delete " states="draft"/> </button> + <button name="button_unlock" position="after"> + <button name="create_bill_dp" string="Create Bill DP" type="object" class="oe_highlight" attrs="{'invisible': [('state', 'not in', ('purchase', 'done'))]}"/> + </button> <field name="date_order" position="before"> <field name="sale_order_id" attrs="{'readonly': [('state', 'not in', ['draft'])]}"/> <field name="sale_order"/> <field name="is_create_uangmuka"/> <field name="approval_status"/> - <field name="amount_total_without_service"/> </field> <field name="approval_status" position="after"> - <field name="revisi_po" attrs="{'readonly': [('state', 'not in', ['draft'])]}"/> + <field name="revisi_po"/> + </field> + <field name="incoterm_id" position="after"> + <field name="amount_total_without_service"/> + <field name="delivery_amt"/> </field> <field name="currency_id" position="after"> <field name="summary_qty_po"/> @@ -88,6 +94,7 @@ <field name="from_apo"/> <field name="approval_edit_line"/> <field name="logbook_bill_id"/> + <field name="bills_dp_id" readonly="1"/> </field> <field name="order_line" position="attributes"> @@ -229,6 +236,9 @@ <field name="product_id"/> <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="margin_so"/> </tree> </field> diff --git a/indoteknik_custom/views/res_users.xml b/indoteknik_custom/views/res_users.xml index 976f46c9..39b9d43e 100644 --- a/indoteknik_custom/views/res_users.xml +++ b/indoteknik_custom/views/res_users.xml @@ -4,7 +4,8 @@ <field name="name">Users: Activation Request</field> <field name="model_id" ref="base.model_res_users"/> <field name="subject">Aktivasi Akun - Indoteknik.com</field> - <field name="email_from">sales@indoteknik.com</field> + <field name="email_from">"Indoteknik.com" <noreply@indoteknik.com></field> + <field name="reply_to">noreply@indoteknik.com</field> <field name="email_to">${object.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;"> @@ -59,5 +60,199 @@ </table> </field> </record> + <record id="mail_template_res_user_company_request" model="mail.template"> + <field name="name">Users: Company Request</field> + <field name="model_id" ref="base.model_res_users"/> + <field name="subject">Email Pendaftaran Bisnis dalam Proses Review</field> + <field name="email_from">"Indoteknik.com" <noreply@indoteknik.com></field> + <field name="reply_to">noreply@indoteknik.com</field> + <field name="email_to">${object.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> + + <tr> + <td colspan="2" 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 --> + <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;"><b>Halo ${object.parent_name},</b></td></tr> + + <tr><td style="padding-bottom: 16px;">Terima kasih atas kepercayaan Anda dengan mendaftarkan bisnis Anda di Indoteknik.com. Permohonan Anda saat ini sedang dalam proses review oleh tim kami.</td></tr> + <tr><td style="padding-bottom: 16px;">Saat ini, kami sedang melakukan pengecekan akhir pada data yang Anda berikan. Proses ini biasanya memakan waktu sekitar 2 x 24 jam.</td></tr> + <tr><td style="padding-bottom: 16px;">Setelah proses review selesai, kami akan segera menginformasikan status akun bisnis Anda melalui email.</td></tr> + <tr><td style="padding-bottom: 16px;">Jika ada pertanyaan lebih lanjut, jangan ragu untuk menghubungi kami di <a href="mailto:sales@indoteknik.com">sales@indoteknik.com</a> atau hubungi whatsapp kami di <a href="https://wa.me/6281717181922">0817-1718-1922</a></td></tr> + <tr><td style="padding-bottom: 16px;">Terima kasih atas perhatiannya.</td></tr> + + <tr><td style="padding-bottom: 2px;"><strong>Hormat kami,</strong></td></tr> + <tr><td style="padding-bottom: 2px;">Indoteknik.com</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> + <record id="mail_template_res_user_company_request_approve" model="mail.template"> + <field name="name">Users: Company Request Approve</field> + <field name="model_id" ref="base.model_res_users"/> + <field name="subject">Email Pendaftaran Bisnis Berhasil</field> + <field name="email_from">"Indoteknik.com" <noreply@indoteknik.com></field> + <field name="reply_to">noreply@indoteknik.com</field> + <field name="email_to">${object.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> + + <tr> + <td colspan="2" 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 --> + <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;"><b>Yth. ${object.parent_name},</b></td></tr> + + <tr><td style="padding-bottom: 16px;">Selamat! Pendaftaran akun bisnis Anda di indoteknik.com telah <b>berhasil diverifikasi & sudah aktif.</b> kini anda dapat menikmati berbagai kemudahan dalam berbelanja, antara lain:</td></tr> + <tr><td style="padding-bottom: 16px;"><b>Fitur Faktur Pajak & Invoice:</b> Dengan mudah download faktur pajak & Invoice Anda secara digital.</td></tr> + <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;">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> + <tr><td style="padding-bottom: 2px;">Indoteknik.com</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> + <record id="mail_template_res_user_company_request_reject" model="mail.template"> + <field name="name">Users: Company Request Reject</field> + <field name="model_id" ref="base.model_res_users"/> + <field name="subject">Email Pendaftaran Bisnis Tidak Berhasil</field> + <field name="email_from">"Indoteknik.com" <noreply@indoteknik.com></field> + <field name="reply_to">noreply@indoteknik.com</field> + <field name="email_to">${object.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> + + <tr> + <td colspan="2" 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 --> + <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;"><b>Yth. ${object.parent_name},</b></td></tr> + + <tr><td style="padding-bottom: 16px;">Terima kasih atas minat Anda untuk mendaftar akun bisnis di Indoteknik.com. Kami telah menerima permohonan pendaftaran Anda dan saat ini sedang dalam proses review.</td></tr> + <tr><td style="padding-bottom: 16px;">Namun, setelah kami lakukan pengecekan, kami menemukan bahwa beberapa informasi yang Anda berikan masih belum lengkap. Untuk dapat melanjutkan proses pendaftaran, mohon kiranya Anda dapat melengkapi data-data berikut:</td></tr> + <tr> + <td style="padding-bottom: 8px; ">Informasi yang kami butuhkan saat ini adalah:</td> + </tr> + <tr> + <td style="padding: 4px 0; text-align: left;"> + <ol style="padding-left: 20px; margin: 0;"> + <li style="padding-bottom: 4px;">Detail Nama Bisnis</li> + <li style="padding-bottom: 4px;">Dokumen NPWP</li> + <li style="padding-bottom: 4px;">Dokumen SPPKP/Surat Pengukuhan Kena Pajak</li> + </ol> + </td> + </tr> + + <tr><td style="padding-bottom: 16px;">Anda dapat mengirimkan informasi yang kurang tersebut melalui email ini atau dengan menghubungi tim layanan pelanggan kami di <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: 16px;">Kami mohon maaf atas ketidaknyamanan ini dan berharap dapat segera menyelesaikan proses pendaftaran akun bisnis Anda.</td></tr> + <tr><td style="padding-bottom: 16px;">Terima kasih atas perhatiannya.</td></tr> + + <tr><td style="padding-bottom: 2px;"><b>Hormat kami,</b></td></tr> + <tr><td style="padding-bottom: 2px;">Indoteknik.com</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> </data> </odoo>
\ No newline at end of file diff --git a/indoteknik_custom/views/sale_order.xml b/indoteknik_custom/views/sale_order.xml index 1257ff85..17faaa95 100755 --- a/indoteknik_custom/views/sale_order.xml +++ b/indoteknik_custom/views/sale_order.xml @@ -32,6 +32,7 @@ <field name="delivery_amt"/> <field name="fee_third_party"/> <field name="total_percent_margin"/> + <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)]}"/> @@ -43,6 +44,17 @@ attrs="{'invisible': ['|', ('applied_voucher_id', '=', False), ('state', 'not in', ['draft','sent'])]}" /> </div> + <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="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" + 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" + attrs="{'invisible': ['|', ('applied_voucher_shipping_id', '=', False), ('state', 'not in', ['draft','sent'])]}" + /> + </div> <button name="calculate_selling_price" string="Calculate Selling Price" type="object" @@ -58,18 +70,22 @@ <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> <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="source_id" domain="[('id', 'in', [32, 59, 60, 61])]" required="1"/> <button name="override_allow_create_invoice" string="Override Create Invoice" type="object" /> + <button string="Estimate Shipping" type="object" name="action_estimate_shipping"/> </field> <field name="partner_shipping_id" position="after"> <field name="real_shipping_id"/> @@ -112,9 +128,11 @@ "/> <field name="purchase_tax_id" attrs="{'readonly': [('parent.approval_status', '!=', False)]}" domain="[('type_tax_use','=','purchase')]"/> <field name="item_percent_margin"/> + <field name="item_margin" 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> @@ -138,6 +156,11 @@ <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> + </div> <field name="total_margin"/> <field name="total_percent_margin"/> </field> diff --git a/indoteknik_custom/views/user_company_request.xml b/indoteknik_custom/views/user_company_request.xml index 2efc1e9b..c9edd3f8 100644 --- a/indoteknik_custom/views/user_company_request.xml +++ b/indoteknik_custom/views/user_company_request.xml @@ -4,7 +4,7 @@ <field name="name">user.company.request.tree</field> <field name="model">user.company.request</field> <field name="arch" type="xml"> - <tree create="0" default_order="is_approve desc"> + <tree create="0" default_order="create_date desc"> <field name="user_id"/> <field name="user_company_id"/> <field name="user_input"/> diff --git a/indoteknik_custom/views/website_categories_management.xml b/indoteknik_custom/views/website_categories_management.xml index 648814e2..6ad85944 100644 --- a/indoteknik_custom/views/website_categories_management.xml +++ b/indoteknik_custom/views/website_categories_management.xml @@ -29,16 +29,16 @@ <group> <field name="sequence"/> <field name="category_id"/> - <field name="category_id2" widget="many2many_tags"/> <field name="status"/> </group> </group> <notebook> - <page string="Detail category"> - <field name="category_id2"> + <page string="Category Level 2 Lines"> + <field name="line_ids"> <tree editable="bottom"> - <field name="name"/> - <field name="child_frontend_id2" widget="many2many_tags"/> + <field name="category_id2" domain="[('parent_frontend_id', '=', parent.category_id)]"/> <!-- Category Level 2 --> + <field name="category_id3_ids" widget="many2many_tags"/> <!-- Category Level 3 --> + <field name="sequence" widget="handle" /> </tree> </field> </page> @@ -48,13 +48,13 @@ </field> </record> -<!-- <record id="ir_actions_server_website_categories_management_sync_to_solr" model="ir.actions.server">--> -<!-- <field name="name">Sync to solr</field>--> -<!-- <field name="model_id" ref="indoteknik_custom.model_website_categories_management"/>--> -<!-- <field name="binding_model_id" ref="indoteknik_custom.model_website_categories_management"/>--> -<!-- <field name="state">code</field>--> -<!-- <field name="code">model.action_sync_to_solr()</field>--> -<!-- </record>--> + <record id="ir_actions_server_website_categories_management_sync_to_solr" model="ir.actions.server"> + <field name="name">Sync to solr</field> + <field name="model_id" ref="indoteknik_custom.model_website_categories_management"/> + <field name="binding_model_id" ref="indoteknik_custom.model_website_categories_management"/> + <field name="state">code</field> + <field name="code">model.action_sync_to_solr()</field> + </record> <menuitem id="website_categories_management" diff --git a/indoteknik_custom/views/website_user_cart.xml b/indoteknik_custom/views/website_user_cart.xml index 11573121..b4fb02be 100755 --- a/indoteknik_custom/views/website_user_cart.xml +++ b/indoteknik_custom/views/website_user_cart.xml @@ -11,13 +11,17 @@ <field name="model">website.user.cart</field> <field name="arch" type="xml"> <tree> + <field name="create_date"/> <field name="user_id"/> <field name="product_id"/> + <field name="phone_user"/> + <field name="price"/> <field name="program_line_id"/> <field name="qty"/> <field name="is_selected"/> <field name="is_reminder"/> <field name="source"/> + <field name="program_product_id" optional="hide"/> </tree> </field> </record> @@ -37,6 +41,9 @@ <field name="is_selected" /> <field name="is_reminder" /> <field name="source" /> + <field name="phone_user"/> + <field name="program_product_id"/> + <field name="price"/> </group> <group></group> </group> |
