from .. import controller from odoo import http, fields from datetime import datetime, timedelta from odoo.http import request import json import logging _logger = logging.getLogger(__name__) class SaleOrder(controller.Controller): prefix = '/api/v1/' PREFIX_PARTNER = prefix + 'partner//' @http.route(prefix + "sale_order//reject/", auth='public', method=['POST', 'OPTIONS'], csrf=False) @controller.Controller.must_authorized() def reject_sale_order_line(self, **kw): so_id = int(kw.get('id', '0')) product_id = int(kw.get('product_id', '0')) params = self.get_request_params(kw, { 'reason_reject': [] }) sale_order_line = request.env['sale.order.line'].search([ ('product_id', '=', product_id), ('order_id', '=', so_id) ], limit=1) if sale_order_line: parameters = { 'sale_order_id': sale_order_line.order_id.id, 'product_id': sale_order_line.product_id.id, 'qty_reject': sale_order_line.product_uom_qty, 'reason_reject': params['value']['reason_reject'], } sale_order_reject = request.env['sales.order.reject'].create(parameters) sale_order_line.unlink() return self.response('work') else: return self.response('Sale order line not found', status=404) @http.route(prefix + "sale_order_number", auth='public', method=['GET', 'OPTIONS']) @controller.Controller.must_authorized() def get_number_sale_order(self, **kw): # for midtrans only sale_order_id = int(kw.get('sale_order_id', '0')) sale_number = str(kw.get('sale_number', '')) if sale_order_id > 0: query = [('id', '=', sale_order_id)] # sales = request.env['sale.order'].search_read([('id', '=', sale_order_id)], fields=['id', 'name', 'amount_total', 'state']) sales = request.env['sale.order'].search(query, limit=1) else: query = [('name', '=', sale_number)] # sales = request.env['sale.order'].search_read([('name', '=', sale_number)], fields=['id', 'name', 'amount_total', 'state']) sales = request.env['sale.order'].search(query, limit=1) data = [] INDONESIAN_MONTHS = { 1: 'Januari', 2: 'Februari', 3: 'Maret', 4: 'April', 5: 'Mei', 6: 'Juni', 7: 'Juli', 8: 'Agustus', 9: 'September', 10: 'Oktober', 11: 'November', 12: 'Desember', } for sale in sales: product_name = '' product_not_in_id = 0 for line in sale.order_line: product_name = line.product_id.name product_not_in_id = line.product_id.id break data.append({ 'id': sale.id, 'name': sale.name, 'date_order': self.time_to_str(sale.date_order, '%d/%m/%Y %H:%M:%S'), 'state': sale.state, 'amount_untaxed': sale.amount_untaxed, 'amount_tax': sale.amount_tax, 'amount_total': sale.amount_total, 'expected_ready_to_ship': f"{sale.expected_ready_to_ship.day} {INDONESIAN_MONTHS[sale.expected_ready_to_ship.month]} {sale.expected_ready_to_ship.year}", # 'eta_date_start': f"{sale.eta_date_start.day} {INDONESIAN_MONTHS[sale.eta_date_start.month]} {sale.eta_date_start.year}", # 'eta_date_end': f"{sale.eta_date.day} {INDONESIAN_MONTHS[sale.eta_date.month]} {sale.eta_date.year}", 'product_name': product_name, 'product_not_in_id': product_not_in_id, 'details': [request.env['sale.order.line'].api_single_response(x, context='with_detail') for x in sale.order_line] }) return self.response(data) @http.route(PREFIX_PARTNER + 'sale_order', auth='public', method=['GET', 'OPTIONS']) @controller.Controller.must_authorized(private=True, private_key='partner_id') def get_partner_sale_order(self, **kw): params = self.get_request_params(kw, { 'partner_id': ['number'], 'name': [], 'site': [], 'limit': ['default:0', 'number'], 'offset': ['default:0', 'number'], 'context': [], 'status': [], 'sort': [], 'startDate': [], 'endDate': [], }) limit = params['value']['limit'] offset = params['value']['offset'] if not params['valid']: return self.response(code=400, description=params) partner_child_ids = self.get_partner_child_ids(params['value']['partner_id']) domain = [('partner_id', 'in', partner_child_ids)] context = params['value']['context'] if context == 'quotation': domain += ["|", "|", ("state", "=", "draft"), ("state", "=", "sent"), ("state", "=", "cancel")] if not context: domain += ["|", ("state", "=", "sale"), ("state", "=", "done")] if params['value']['name']: name = params['value']['name'].replace(' ', '%') order_lines = request.env['sale.order.line'].search([ ('order_id.partner_id', 'in', partner_child_ids), '|', ('product_id.name', 'ilike', name), ('product_id.default_code', 'ilike', name), ]) sale_order_ids_from_lines = order_lines.mapped('order_id.id') domain += ['|', '|', ('name', 'ilike', name), ('partner_purchase_order_name', 'ilike', name), ('id', 'in', sale_order_ids_from_lines) ] if params['value']['site']: site = params['value']['site'].replace(' ', '%') domain += [('partner_id.site_id.name', 'ilike', '%' + site + '%')] status = params['value'].get('status') if status: if status == 'quotation': domain += [('state', '=', 'draft')] domain += [('approval_status', '=', False)] elif status == 'cancel': domain += [('state', '=', 'cancel')] elif status == 'belum_bayar': domain += [ ('state', '=', 'draft'), ('approval_status', 'in', ['pengajuan0','pengajuan1', 'pengajuan2']), ('payment_status', 'in', [False, None, '', 'pending', 'expire']) ] elif status == 'diproses': domain += [ ('state', '=', 'draft'), ('approval_status', 'in', ['pengajuan0','pengajuan1', 'pengajuan2']), ('payment_status', '!=', False), ('payment_status', 'not in', ['', 'pending', 'expire']), ] elif status in ['dikemas', 'dikirim', 'selesai', 'partial']: domain += [('state', '=', 'sale')] elif status == 'all': domain += [] # Sorting order = None if params['value']['sort']: if params['value']['sort'] == 'asc': order = 'amount_total asc' elif params['value']['sort'] == 'desc': order = 'amount_total desc' # Filter tanggal try: if params['value']['startDate'] and params['value']['endDate']: start_date = datetime.strptime(params['value']['startDate'], '%d/%m/%Y').strftime('%Y-%m-%d 00:00:00') end_date = datetime.strptime(params['value']['endDate'], '%d/%m/%Y').strftime('%Y-%m-%d 23:59:59') else: start_date = '2023-01-01 00:00:00' end_date = fields.Datetime.now().strftime('%Y-%m-%d 23:59:59') domain.append(('date_order', '>=', start_date)) domain.append(('date_order', '<=', end_date)) except ValueError: return self.response(code=400, description="Invalid date format. Use 'DD/MM/YYYY'.") sale_orders = request.env['sale.order'].search(domain, order=order) # Filter status pengiriman status = params['value'].get('status') if status in ['dikemas', 'dikirim', 'selesai', 'partial']: filtered_orders = [] for sale_order in sale_orders: bu_pickings = [p for p in sale_order.picking_ids if p.picking_type_id and p.picking_type_id.id == 29 and p.state != 'cancel'] total = len(bu_pickings) done_pickings = [p for p in bu_pickings if p.state == 'done'] done_with_driver = [p for p in done_pickings if p.sj_return_date] done_without_driver = [p for p in done_pickings if not p.sj_return_date] if status == 'dikemas' and len(done_pickings) == 0: filtered_orders.append(sale_order) elif status == 'dikirim' and len(done_pickings) == total and len(done_pickings) > 0 and len( done_without_driver) == total: filtered_orders.append(sale_order) elif status == 'selesai' and len(done_pickings) == total and len(done_pickings) > 0 and len( done_with_driver) == total: filtered_orders.append(sale_order) elif status == 'partial' and ( len(done_pickings) != total or (done_with_driver and done_without_driver)): filtered_orders.append(sale_order) else: filtered_orders = sale_orders filtered_orders_paginated = filtered_orders[offset: offset + limit] # === Ringkasan (tanpa auto-generate) === CBD_PAYMENT_TERM_ID = 26 ALLOWED_CONTINUE = {'', 'pending', 'capture', 'expire', 'cancel'} # boleh munculkan "Bayar Sekarang" def _is_website_order(so): return bool(so.source_id and so.source_id.id == 59) sale_orders_payload = [] for so in filtered_orders_paginated: item = request.env['sale.order'].api_v1_single_response(so) approval_ok = (so.approval_status in ('pengajuan1', 'pengajuan2')) source_ok = _is_website_order(so) term_ok = bool(so.payment_term_id and so.payment_term_id.id == CBD_PAYMENT_TERM_ID) pay_status = (getattr(so, 'payment_status', '') or '').strip().lower() eligible = bool(approval_ok and source_ok and term_ok and pay_status in ALLOWED_CONTINUE) redirect_url = getattr(so, 'payment_link_midtrans', '') or '' item.update({ 'eligibleContinue': eligible, 'paymentSummary': { 'eligible': eligible, 'approvalStatus': so.approval_status or '', 'paymentStatus': pay_status, 'paymentTermId': so.payment_term_id.id if so.payment_term_id else None, 'sourceId': so.source_id.id if so.source_id else None, 'redirectUrl': redirect_url, } }) sale_orders_payload.append(item) data = { 'sale_order_total': len(filtered_orders), 'sale_orders': sale_orders_payload } return self.response(data) @http.route(PREFIX_PARTNER + 'sale_order/', auth='public', method=['GET', 'OPTIONS']) @controller.Controller.must_authorized() def partner_get_sale_order_detail(self, **kw): params = self.get_request_params(kw, { 'partner_id': ['number'], 'id': ['number'], 'ensure_payment_link': [], # optional flag }) if not params['valid']: return self.response(code=400, description=params) partner_child_ids = self.get_partner_child_ids(params['value']['partner_id']) domain = [('id', '=', params['value']['id']), ('partner_id', 'in', partner_child_ids)] data = {} sale_order = request.env['sale.order'].search(domain) if sale_order: data = request.env['sale.order'].api_v1_single_response(sale_order, context='with_detail') CBD_PAYMENT_TERM_ID = 26 ALLOWED_CONTINUE = {'', 'pending', 'capture', 'expire', 'cancel'} def _is_website_order(so): return bool(so.source_id and so.source_id.id == 59) pay_status = (getattr(sale_order, 'payment_status', '') or '').strip().lower() eligible = ( sale_order.approval_status in ('pengajuan1', 'pengajuan2') and _is_website_order(sale_order) and sale_order.payment_term_id and sale_order.payment_term_id.id == CBD_PAYMENT_TERM_ID and pay_status in ALLOWED_CONTINUE ) redirect_url = getattr(sale_order, 'payment_link_midtrans', '') or '' ensure_raw = params['value'].get('ensure_payment_link') ensure_flag = False if ensure_raw is not None: s = str(ensure_raw).strip().lower() ensure_flag = s not in ('', '0', 'false', 'no', 'none') if ensure_flag and eligible: should_generate = False if not redirect_url: should_generate = True elif pay_status in ('expire', 'cancel'): should_generate = True if should_generate: try: sale_order.sudo().generate_payment_link_midtrans_sales_order() redirect_url = getattr(sale_order, 'payment_link_midtrans', '') or '' except Exception as e: _logger.warning(f'Generate Midtrans gagal untuk SO {sale_order.id}: {e}') data.update({ 'eligible_continue': eligible, 'payment_summary': { 'eligible': eligible, 'approval_status': sale_order.approval_status or '', 'payment_status': pay_status, 'payment_term_id': sale_order.payment_term_id.id if sale_order.payment_term_id else None, 'source_id': sale_order.source_id.id if sale_order.source_id else None, 'redirect_url': redirect_url, } }) # formatting tanggal (tetap sama) if sale_order.expected_ready_to_ship: bulan_id = [ "Januari", "Februari", "Maret", "April", "Mei", "Juni", "Juli", "Agustus", "September", "Oktober", "November", "Desember" ] tanggal = sale_order.expected_ready_to_ship.day bulan = bulan_id[sale_order.expected_ready_to_ship.month - 1] tahun = sale_order.expected_ready_to_ship.year data['expected_ready_to_ship'] = f"{tanggal} {bulan} {tahun}" if sale_order.eta_date_start: bulan_id = [ "Januari", "Februari", "Maret", "April", "Mei", "Juni", "Juli", "Agustus", "September", "Oktober", "November", "Desember" ] tanggal = sale_order.eta_date_start.day bulan = bulan_id[sale_order.eta_date_start.month - 1] tahun = sale_order.eta_date_start.year data['eta_date_start'] = f"{tanggal} {bulan} {tahun}" if sale_order.eta_date: bulan_id = [ "Januari", "Februari", "Maret", "April", "Mei", "Juni", "Juli", "Agustus", "September", "Oktober", "November", "Desember" ] tanggal = sale_order.eta_date.day bulan = bulan_id[sale_order.eta_date.month - 1] tahun = sale_order.eta_date.year data['eta_date_end'] = f"{tanggal} {bulan} {tahun}" return self.response(data) @http.route(PREFIX_PARTNER + 'sale_order//checkout', auth='public', method=['POST', 'OPTIONS'], csrf=False) @controller.Controller.must_authorized(private=True, private_key='partner_id') def partner_checkout_sale_order_by_id(self, **kw): params = self.get_request_params(kw, { 'partner_id': ['number'], 'id': ['number'], 'status': ['boolean'] }) if not params['valid']: return self.response(code=400, description=params) partner_child_ids = self.get_partner_child_ids(params['value']['partner_id']) domain = [ ('id', '=', params['value']['id']), ('partner_id', 'in', partner_child_ids) ] data = {} sale_order = request.env['sale.order'].search(domain) if sale_order: if 'status' in params['value']: sale_order.is_continue_transaction = params['value']['status'] if sale_order._requires_approval_margin_leader(): sale_order.approval_status = 'pengajuan2' elif sale_order._requires_approval_margin_manager(): sale_order.approval_status = 'pengajuan1' data = request.env['sale.order'].api_v1_single_response( sale_order, context='with_detail') return self.response(data) @http.route(PREFIX_PARTNER + 'sale_order//approve', auth='public', method=['POST', 'OPTIONS'], csrf=False) @controller.Controller.must_authorized(private=True, private_key='partner_id') def partner_approve_sale_order_by_id(self, **kw): params = self.get_request_params(kw, { 'partner_id': ['number'], 'id': ['number'] }) if not params['valid']: return self.response(code=400, description=params) value = params['value'] partner_child_ids = self.get_partner_child_ids(value['partner_id']) sale_order = request.env['sale.order'].search([ ('id', '=', value['id']), ('partner_id', 'in', partner_child_ids) ]) if not sale_order: return self.response(code=404, description='Sale Order not found') partner = request.env['res.partner'].browse(value['partner_id']) if not partner.web_role: return self.response(code=400, description='Unauthorized') if partner.web_role: sale_order.web_approval = 'cust_%s' % partner.web_role if sale_order.web_approval in ['cust_procurement', 'cust_director']: if sale_order._requires_approval_margin_leader(): sale_order.approval_status = 'pengajuan2' elif sale_order._requires_approval_margin_manager(): sale_order.approval_status = 'pengajuan1' return self.response('success') @http.route(PREFIX_PARTNER + 'sale_order//reject', auth='public', method=['POST', 'OPTIONS'], csrf=False) @controller.Controller.must_authorized(private=True, private_key='partner_id') def partner_reject_sale_order_by_id(self, **kw): params = self.get_request_params(kw, { 'partner_id': ['number'], 'id': ['number'] }) if not params['valid']: return self.response(code=400, description=params) value = params['value'] partner_child_ids = self.get_partner_child_ids(value['partner_id']) sale_order = request.env['sale.order'].search([ ('id', '=', value['id']), ('partner_id', 'in', partner_child_ids) ]) if not sale_order: return self.response(code=404, description='Sale Order not found') partner = request.env['res.partner'].browse(value['partner_id']) if not partner.web_role: return self.response(code=400, description='Unauthorized') if partner.web_role: sale_order.web_approval = 'cust_%s' % partner.web_role sale_order.state = 'cancel' sale_order.approval_status = False return self.response('success') @http.route(PREFIX_PARTNER + 'sale_order//upload_po', auth='public', method=['POST', 'OPTIONS'], csrf=False) def partner_upload_po_sale_order(self, **kw): user_token = self.authenticate() if not user_token: return self.unauthorized_response() params = self.get_request_params(kw, { 'partner_id': ['number'], 'id': ['number'], 'name': [], 'file': [] }) if not user_token['partner_id'] == params['value']['partner_id']: return self.unauthorized_response() if not params['valid']: return self.response(code=400, description=params) partner_child_ids = self.get_partner_child_ids( params['value']['partner_id']) domain = [ ('id', '=', params['value']['id']), ('partner_id', 'in', partner_child_ids) ] data = False sale_order = request.env['sale.order'].search(domain) if sale_order: sale_order.partner_purchase_order_name = params['value']['name'] sale_order.client_order_ref = params['value']['name'] sale_order.partner_purchase_order_file = params['value']['file'] data = sale_order.id return self.response(data) @http.route(PREFIX_PARTNER + 'sale_order//download_po/', auth='none', method=['GET']) def partner_download_po_sale_order(self, id, token): id = int(id) rest_api = request.env['rest.api'] md5_valid = rest_api.md5_salt_valid(id, 'sale.order', token) if not md5_valid: return self.response('Unauthorized') sale_order = request.env['sale.order'].sudo().search_read( [('id', '=', id)], ['partner_purchase_order_name']) attachment = rest_api.get_single_attachment( 'sale.order', 'partner_purchase_order_file', id) if attachment and len(sale_order) > 0: return rest_api.response_attachment({ 'content': attachment['datas'], 'decode_content': True, 'mimetype': attachment['mimetype'], 'filename': sale_order[0]['partner_purchase_order_name'] + '.pdf' }) return self.response('Dokumen tidak ditemukan', code=404) @http.route(PREFIX_PARTNER + 'sale_order//download/', auth='none', method=['GET']) def partner_download_sale_order(self, id, token): id = int(id) rest_api = request.env['rest.api'] md5_valid = rest_api.md5_salt_valid(id, 'sale.order', token) if not md5_valid: return self.response('Unauthorized') sale_order = request.env['sale.order'].sudo().search_read([('id', '=', id)], ['name']) # pdf, type = request.env['ir.actions.report'].sudo().search([('report_name', '=', 'quotation_so_new')]).render_jasper([id], {}) pdf, type = request.env['ir.actions.report'].sudo().search([('report_name', '=', 'indoteknik_custom.report_saleorder_website')])._render_qweb_pdf([id]) if pdf and len(sale_order) > 0: return rest_api.response_attachment({ 'content': pdf, 'mimetype': 'application/pdf', 'filename': sale_order[0]['name'] + '.pdf' }) return self.response('Dokumen tidak ditemukan', code=404) @http.route(PREFIX_PARTNER + 'sale_order//cancel', auth='public', method=['POST', 'OPTIONS'], csrf=False) @controller.Controller.must_authorized(private=True, private_key='partner_id') def partner_cancel_sale_order(self, **kw): params = self.get_request_params(kw, { 'partner_id': ['number'], 'id': ['number'] }) if not params['valid']: return self.response(code=400, description=params) partner_child_ids = self.get_partner_child_ids( params['value']['partner_id']) domain = [ ('id', '=', params['value']['id']), ('partner_id', 'in', partner_child_ids) ] data = False sale_order = request.env['sale.order'].search(domain) if sale_order: sale_order.state = 'cancel' data = sale_order.id return self.response(data) @http.route(prefix + 'user//sale_order/checkout', auth='public', method=['GET', 'OPTIONS'], csrf=False) @controller.Controller.must_authorized(private=True, private_key='user_id') def get_user_checkout_so(self, user_id, **kw): m_voucher = request.env['voucher'] m_cart = request.env['website.user.cart'] voucher_code = kw.get('voucher') voucher_shipping_code = kw.get('voucher_shipping') source = kw.get('source') voucher = m_voucher.search([('code', '=', voucher_code), ('apply_type', 'in', ['all', 'brand'])], limit=1) voucher_shipping = m_voucher.search([('code', '=', voucher_shipping_code), ('apply_type', '=', 'shipping')], limit=1) result = m_cart.with_context(price_for="web").get_user_checkout( user_id, voucher=voucher, voucher_shipping=voucher_shipping, source=source ) return self.response(result) @http.route(PREFIX_PARTNER + 'sale_order/checkout', auth='public', method=['POST', 'OPTIONS'], csrf=False) @controller.Controller.must_authorized() def create_partner_sale_order(self, **kw): _logger.info("=== START CREATE PARTNER SALE ORDER ===") try: config = request.env['ir.config_parameter'] product_pricelist_default_discount_id = int(config.get_param('product.pricelist.tier1_v2')) user_pricelist = request.env.context.get('user_pricelist').id or False _logger.info( f"Config - Default Pricelist: {product_pricelist_default_discount_id}, User Pricelist: {user_pricelist}") params = self.get_request_params(kw, { 'user_id': ['number'], 'partner_id': ['number'], 'partner_shipping_id': ['required', 'number'], 'partner_invoice_id': ['required', 'number'], 'order_line': ['required', 'default:[]'], 'po_number': [], 'po_file': [], 'type': [], 'delivery_amount': ['number', 'default:0'], 'carrier_id': [], 'delivery_service_type': [], 'flash_sale': ['boolean'], 'note_website': [], 'voucher': [], 'source': [], 'estimated_arrival_days': ['number', 'default:0'], 'estimated_arrival_days_start': ['number', 'default:0'] }) _logger.info(f"Raw input params: {kw}") _logger.info(f"Processed params: {params}") if not params['valid']: _logger.error(f"Invalid params: {params}") return self.response(code=400, description=params) # Fetch partner details sales_partner = request.env['res.partner'].browse(params['value']['partner_id']) partner_invoice = request.env['res.partner'].browse(params['value']['partner_invoice_id']) main_partner = partner_invoice.get_main_parent() _logger.info( f"Partner Info - Sales: {sales_partner.id}, Invoice: {partner_invoice.id}, Main: {main_partner.id}") def _get_request_context(params, kw): # 1) kw (querystring di route) ctx = kw.get('context') if ctx: return str(ctx).strip().lower() # 2) querystring langsung ctx = request.httprequest.args.get('context') if ctx: return str(ctx).strip().lower() # 3) form-encoded body ctx = request.httprequest.form.get('context') if ctx: return str(ctx).strip().lower() # 4) JSON body (kalau ada) try: js = request.httprequest.get_json(force=False, silent=True) or {} ctx = js.get('context') if ctx: return str(ctx).strip().lower() except Exception: pass # 5) fallback: dari hasil get_request_params kalau kamu tambahkan 'context' di mapping try: return str((params.get('value', {}) or {}).get('context') or '').strip().lower() except Exception: return '' ctx = _get_request_context(params, kw) _logger.info(f"[checkout] context sources -> kw={repr(kw.get('context'))}, " f"args={repr(request.httprequest.args.get('context'))}, " f"form={repr(request.httprequest.form.get('context'))}, " f"json={(request.httprequest.get_json(force=False, silent=True) or {}).get('context') if hasattr(request.httprequest,'get_json') else None}, " f"normalized={ctx}") payment_term_id_value = 26 ctx = str((kw.get('context') or '')) if ctx == 'quotation': try: creator_user_id = params['value'].get('user_id') or request.env.user.id user = request.env['res.users'].sudo().browse(int(creator_user_id)) term_name = None if user and user.exists() and user.partner_id: prop = getattr(user.partner_id, 'property_payment_term_id', False) # Kalau Many2one -> pakai .name, kalau string/selection -> cast ke str if prop: term_name = getattr(prop, 'name', False) or str(prop).strip() if term_name: term = request.env['account.payment.term'].sudo().search( [('name', 'ilike', term_name)], limit=1 ) if term: payment_term_id_value = term.id except Exception as e: _logger.warning(f"Gagal resolve payment term dari user: {e}") parameters = { 'warehouse_id': 8, 'carrier_id': 1, 'sales_tax_id': 23, 'pricelist_id': user_pricelist or product_pricelist_default_discount_id, 'payment_term_id':payment_term_id_value, 'team_id': 2, 'company_id': 1, 'currency_id': 12, 'source_id': 59, 'state': 'draft', 'picking_policy': 'direct', 'partner_id': params['value']['partner_id'], 'partner_shipping_id': params['value']['partner_shipping_id'], 'real_shipping_id': params['value']['partner_shipping_id'], 'partner_invoice_id': main_partner.id, 'real_invoice_id': params['value']['partner_invoice_id'], 'partner_purchase_order_name': params['value']['po_number'], 'partner_purchase_order_file': params['value']['po_file'], 'delivery_amt': params['value']['delivery_amount'] * 1.10, 'estimated_arrival_days': params['value']['estimated_arrival_days'], 'estimated_arrival_days_start': params['value']['estimated_arrival_days_start'], 'shipping_cost_covered': 'customer', 'shipping_paid_by': 'customer', 'carrier_id': params['value']['carrier_id'], 'delivery_service_type': params['value']['delivery_service_type'], 'flash_sale': params['value']['flash_sale'], 'note_website': params['value']['note_website'], 'customer_type': sales_partner.customer_type if sales_partner else 'nonpkp', 'npwp': sales_partner.npwp or '0', 'sppkp': sales_partner.sppkp, 'email': sales_partner.email, 'user_id': 11314 } _logger.info(f"Order parameters: {parameters}") sales_partner = request.env['res.partner'].browse(parameters['partner_id']) if sales_partner and sales_partner.user_id and sales_partner.user_id.id not in [25]: parameters['user_id'] = sales_partner.user_id.id _logger.info(f"Updated user_id from partner: {parameters['user_id']}") if params['value']['type'] == 'sale_order': parameters['approval_status'] = 'pengajuan1' # parameters['approval_status'] = False _logger.info("Setting approval_status to 'pengajuan1'") sale_order = request.env['sale.order'].with_context(from_website_checkout=True).create([parameters]) sale_order.onchange_partner_contact() _logger.info(f"Created SO: {sale_order.id} - {sale_order.name}") user_id = params['value']['user_id'] user_cart = request.env['website.user.cart'] source = params['value']['source'] _logger.info(f"Getting cart for user: {user_id}, source: {source}") carts = user_cart.get_product_by_user(user_id=user_id, selected=True, source=source) _logger.info(f"Found {len(carts)} cart items") promotions = [] for idx, cart in enumerate(carts, 1): _logger.info(f"\n=== Processing Cart Item {idx}/{len(carts)} ===") _logger.info(f"Full cart data: {cart}") if cart['cart_type'] == 'product': product = request.env['product.product'].browse(cart['id']) _logger.info(f"Product: {product.id} - {product.name}") _logger.info(f"Cart Price Data: {cart['price']}") # Determine discount status based on: # 1. has_flashsale flag from cart data # 2. discount percentage > 0 # 3. global flash sale parameter is_flash_sale_item = cart.get('has_flashsale', False) discount_percent = float(cart['price'].get('discount_percentage', 0)) global_flash_sale = params['value'].get('flash_sale', False) is_has_disc = False # Item is considered discounted if: # - It's specifically marked as flash sale item, OR # - It has significant discount (>0%) and not affected by global flash sale if is_flash_sale_item: is_has_disc = True _logger.info("Item is flash sale product - marked as discounted") elif global_flash_sale: _logger.info("Global flash sale active but item not eligible - not marked as discounted") _logger.info(f"Final is_has_disc: {is_has_disc}") order_line = request.env['sale.order.line'].create({ 'company_id': 1, 'order_id': sale_order.id, 'product_id': product.id, 'product_uom_qty': cart['quantity'], 'product_available_quantity': cart['available_quantity'], 'price_unit': cart['price']['price'], 'discount': discount_percent, 'is_has_disc': is_has_disc }) _logger.info(f"Created order line: {order_line.id}") order_line.product_id_change() order_line.weight = order_line.product_id.weight order_line._onchange_vendor_id_custom() _logger.info(f"After onchanges - Price: {order_line.price_unit}, Disc: {order_line.discount}") elif cart['cart_type'] == 'promotion': promotions.append({ 'order_id': sale_order.id, 'program_line_id': cart['id'], 'quantity': cart['quantity'] }) _logger.info(f"Added promotion: {cart['id']}") _logger.info("Processing promotions...") sale_order._compute_etrts_date() request.env['sale.order.promotion'].create(promotions) if len(promotions) > 0: _logger.info(f"Applying {len(promotions)} promotions") sale_order.apply_promotion_program() sale_order.add_free_product(promotions) # Pastikan baris hasil promo/bonus ditandai supaya bisa di-skip voucher promo_lines = sale_order.order_line.filtered( lambda l: getattr(l, 'order_promotion_id', False) or (l.price_unit or 0.0) == 0.0 ) if promo_lines: promo_lines.write({'is_has_disc': True}) _logger.info(f"[PROMO_MARK] Marked {len(promo_lines)} promo/free lines as is_has_disc=True") voucher_code = params['value']['voucher'] if voucher_code: _logger.info(f"Processing voucher: {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: _logger.info("Applying regular voucher") sale_order.voucher_id = voucher.id sale_order.apply_voucher() if voucher_shipping and len(promotions) == 0: _logger.info("Applying shipping voucher") 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(): sale_order.approval_status = 'pengajuan2' _logger.info("Approval status set to 'pengajuan2'") elif sale_order._requires_approval_margin_manager(): sale_order.approval_status = 'pengajuan1' _logger.info("Approval status set to 'pengajuan1'") sale_order._auto_set_shipping_from_website() _logger.info("=== END CREATE PARTNER SALE ORDER ===") return self.response({ 'id': sale_order.id, 'name': sale_order.name }) except Exception as e: _logger.error(f"Error in create_partner_sale_order: {str(e)}", exc_info=True) return self.response(code=500, description=str(e)) @http.route(PREFIX_PARTNER + 'sale-order//awb', auth='public', methods=['GET', 'OPTIONS']) @controller.Controller.must_authorized(private=True, private_key='partner_id') def get_airway_bill_by_sale_order_id(self, **kw): id = int(kw.get('id', 0)) response = {'airways': None} airway_bills = request.env['airway.bill'].search([('so_id', '=', id)]) if not airway_bills: return self.response(response) airways = [] for airway_bill in airway_bills: data = airway_bill.decode_response() delivery_order = airway_bill.do_id result = data['rajaongkir']['result'] manifests_data = [] for manifest in airway_bill.manifest_ids: manifest_data = { 'code': manifest.code, 'description': manifest.description, 'datetime': datetime.strftime(manifest.datetime, '%Y-%m-%d %H:%M:%S'), 'city': manifest.city, } manifests_data.append(manifest_data) airways.append({ 'delivery_order': { 'name': delivery_order.name, 'carrier': delivery_order.carrier_id.name, 'receiver_name': airway_bill.receiver_name, 'receiver_city': airway_bill.receiver_city, }, 'delivered': result['delivered'], 'waybill_number': result['summary']['waybill_number'], 'delivery_status': result['delivery_status'], 'manifests': manifests_data }) response = {'airways': airways} return self.response(response) @http.route('/api/sale_order/invoiced', auth='public', methods=['GET']) def get_sale_order_invoiced_by_partner_id(self, **kw): if not self.authenticate(): return self.unauthorized_response() partner_id = kw.get('partner_id') if not partner_id: return self.response(code=400, description='Field partner_id is required') partner_id = int(partner_id) # Get company member by partner_id parent_partner_id = request.env['res.partner'].search( [('id', '=', partner_id)], limit=1).parent_id.id partner_childs = request.env['res.partner'].search( [('parent_id', '=', int(parent_partner_id))]) partner_child_ids = [v['id'] for v in partner_childs] + [partner_id] # Get sale order by company member and invoiced data = [] default_domain = [ ('partner_id', 'in', partner_child_ids), ('state', 'in', ['sale', 'done']) ] sale_orders = self.search_filter('sale.order', kw, default_domain) for sale_order in sale_orders: pickings = [] for picking in sale_order.picking_ids: if picking.state in ['confirmed', 'assigned', 'done']: pickings.append({ 'id': picking.id, 'name': picking.name, 'delivery_address': picking.partner_id.street, 'delivery_tracking_no': picking.delivery_tracking_no, 'delivery_status': picking.delivery_status }) data.append({ 'id': sale_order.id, 'name': sale_order.name, 'amount_total': sale_order.amount_total, 'salesperson': sale_order.user_id.name, 'date_order': self.time_to_str(sale_order.date_order, '%d/%m/%Y'), 'pickings': pickings, 'access_token': sale_order.access_token }) return self.response(data) @http.route('/api/sale_order/invoiced/detail', auth='public', methods=['GET']) def get_sale_order_invoiced_detail_by_partner(self, **kw): if not self.authenticate(): return self.unauthorized_response() id = kw.get('id') partner_id = kw.get('partner_id') if not id: return self.response(code=400, description='Field id is required') if not partner_id: return self.response(code=400, description='Field partner_id is required') default_domain = [ ('id', '=', id), ('state', 'in', ['sale', 'done']) ] parent_partner_id = request.env['res.partner'].search( [('id', '=', int(partner_id))], limit=1).parent_id.id partner_childs = request.env['res.partner'].search( [('parent_id', '=', int(parent_partner_id))]) partner_child_ids = [v['id'] for v in partner_childs] + [int(partner_id)] default_domain.append(('partner_id', 'in', partner_child_ids)) sale_order = self.search_filter('sale.order', kw, default_domain) orders = [] for order in sale_order.order_line: orders.append({ 'name': order.name, 'product_qty': order.product_qty, 'price_unit': order.price_unit, 'price_tax': order.price_tax, 'price_total': order.price_total, 'price_subtotal': order.price_subtotal, 'tax': order.tax_id.name, 'discount': order.discount, }) data = { 'id': sale_order.id, 'name': sale_order.name, 'carrier': sale_order.carrier_id.name, 'partner': { 'id': sale_order.partner_id.id, 'name': sale_order.partner_id.name, 'mobile': sale_order.partner_id.mobile, 'email': sale_order.partner_id.email }, 'delivery_address': sale_order.partner_shipping_id.street, 'delivery_method': sale_order.carrier_id.name, 'payment_term': sale_order.payment_term_id.name, 'salesperson': sale_order.user_id.name, 'date_order': self.time_to_str(sale_order.date_order, '%d/%m/%Y %H:%M:%S'), 'note': sale_order.note, 'amount_untaxed': sale_order.amount_untaxed, 'amount_tax': sale_order.amount_tax, 'amount_total': sale_order.amount_total, 'orders': orders } return self.response(data) @http.route(prefix + 'tracking_order', auth='public', method=['GET', 'OPTIONS']) @controller.Controller.must_authorized() def tracking_get_sale_order_detail(self, **kw): # Extract 'so' and 'email' parameters from query parameters so = kw.get('so') email_user = kw.get('email') if not email_user or not so: return self.response({ 'code': 400, 'so': so, 'email': email_user, 'description': "Email and Sale Order number are required." }) # Search for the sale order by the name (so) sale_order = request.env['sale.order'].search([('name', '=', so)], limit=1) if not sale_order: return self.response({ 'code': 404, 'so': so, 'email': email_user, 'description': "Sale Order not found." }) # Get the partner associated with the sale order partner = sale_order.partner_id company_id = partner.company_id.id # Search for all partners within the same company partners_in_company = request.env['res.partner'].search([('company_id', '=', company_id)]) # Check if the email matches any partner's email in the same company email_match = partners_in_company.filtered(lambda p: p.email == email_user) if not email_match: return self.response({ 'code': 403, 'so': so, 'email': email_user, 'description': "Email does not match any partner in the same company as the Sale Order." }) # Check for partner child ids if needed partner_child_ids = self.get_partner_child_ids(partner.id) if sale_order.partner_id.id not in partner_child_ids: return self.response({ 'code': 403, 'so': so, 'email': email_user, 'description': "Unauthorized access to Sale Order details." }) # Prepare the response data data = request.env['sale.order'].api_v1_single_response(sale_order, context='with_detail') return self.response(data)