from odoo import fields, models, api, _ from odoo.exceptions import AccessError, UserError, ValidationError from odoo.tools.float_utils import float_is_zero from itertools import groupby import pytz, datetime class StockPicking(models.Model): _inherit = 'stock.picking' is_internal_use = fields.Boolean('Internal Use', help='flag which is internal use or not') account_id = fields.Many2one('account.account', string='Account') efaktur_id = fields.Many2one('vit.efaktur', string='Faktur Pajak') is_efaktur_exported = fields.Boolean(string='Is eFaktur Exported') date_efaktur_exported = fields.Datetime(string='eFaktur Exported Date') delivery_status = fields.Char(string='Delivery Status', compute='compute_delivery_status', readonly=True) summary_qty_detail = fields.Float('Total Qty Detail', compute='_compute_summary_qty') summary_qty_operation = fields.Float('Total Qty Operation', compute='_compute_summary_qty') count_line_detail = fields.Float('Total Item Detail', compute='_compute_summary_qty') count_line_operation = fields.Float('Total Item Operation', compute='_compute_summary_qty') real_shipping_id = fields.Many2one( 'res.partner', string='Real Delivery Address', domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", help="Dipakai untuk alamat tempel") # Delivery Order driver_departure_date = fields.Datetime( string='Driver Departure Date', readonly=True, copy=False ) driver_arrival_date = fields.Datetime( string='Driver Arrival Date', readonly=True, copy=False ) delivery_tracking_no = fields.Char( string='Delivery Tracking Number', readonly=True, copy=False ) driver_id = fields.Many2one( comodel_name='res.users', string='Driver', readonly=True, copy=False ) picking_code = fields.Char( string="Picking Code", readonly=True, copy=False ) approval_status = fields.Selection([ ('pengajuan1', 'Approval Accounting'), ('approved', 'Approved'), ], string='Approval Status', readonly=True, copy=False, index=True, tracking=3, help="Approval Status untuk Internal Use") approval_receipt_status = fields.Selection([ ('pengajuan1', 'Approval Logistic'), ('approved', 'Approved'), ], string='Approval Receipt Status', readonly=True, copy=False, index=True, tracking=3, help="Approval Status untuk Receipt") approval_return_status = fields.Selection([ ('pengajuan1', 'Approval Finance'), ('approved', 'Approved'), ], string='Approval Return Status', readonly=True, copy=False, index=True, tracking=3, help="Approval Status untuk Return") date_doc_kirim = fields.Datetime(string='Tanggal Kirim di SJ', help="Tanggal Kirim di cetakan SJ, tidak berpengaruh ke Accounting") note_logistic = fields.Selection([ ('hold', 'Hold by Sales'), ('not_paid', 'Customer belum bayar') ], string='Note', help='jika field ini diisi maka tidak akan dihitung ke lead time') waybill_id = fields.One2many(comodel_name='airway.bill', inverse_name='do_id', string='Airway Bill') def action_create_invoice_from_mr(self): """Create the invoice associated to the PO. """ if not self.env.user.is_accounting: raise UserError('Hanya Accounting yang bisa membuat Bill') precision = self.env['decimal.precision'].precision_get('Product Unit of Measure') #custom here po = self.env['purchase.order'].search([ ('name', '=', self.group_id.name) ]) # 1) Prepare invoice vals and clean-up the section lines invoice_vals_list = [] for order in po: if order.invoice_status != 'to invoice': continue order = order.with_company(order.company_id) pending_section = None # Invoice values. invoice_vals = order._prepare_invoice() # Invoice line values (keep only necessary sections). for line in self.move_ids_without_package: po_line = self.env['purchase.order.line'].search([('order_id', '=', po.id), ('product_id', '=', line.product_id.id)], limit=1) qty = line.product_uom_qty if po_line.display_type == 'line_section': pending_section = line continue if not float_is_zero(po_line.qty_to_invoice, precision_digits=precision): if pending_section: invoice_vals['invoice_line_ids'].append((0, 0, pending_section._prepare_account_move_line_from_mr(po_line, qty))) pending_section = None invoice_vals['invoice_line_ids'].append((0, 0, line._prepare_account_move_line_from_mr(po_line, qty))) invoice_vals_list.append(invoice_vals) if not invoice_vals_list: raise UserError(_('There is no invoiceable line. If a product has a control policy based on received quantity, please make sure that a quantity has been received.')) # 2) group by (company_id, partner_id, currency_id) for batch creation new_invoice_vals_list = [] for grouping_keys, invoices in groupby(invoice_vals_list, key=lambda x: (x.get('company_id'), x.get('partner_id'), x.get('currency_id'))): origins = set() payment_refs = set() refs = set() ref_invoice_vals = None for invoice_vals in invoices: if not ref_invoice_vals: ref_invoice_vals = invoice_vals else: ref_invoice_vals['invoice_line_ids'] += invoice_vals['invoice_line_ids'] origins.add(invoice_vals['invoice_origin']) payment_refs.add(invoice_vals['payment_reference']) refs.add(invoice_vals['ref']) ref_invoice_vals.update({ 'ref': ', '.join(refs)[:2000], 'invoice_origin': ', '.join(origins), 'payment_reference': len(payment_refs) == 1 and payment_refs.pop() or False, }) new_invoice_vals_list.append(ref_invoice_vals) invoice_vals_list = new_invoice_vals_list # 3) Create invoices. moves = self.env['account.move'] AccountMove = self.env['account.move'].with_context(default_move_type='in_invoice') for vals in invoice_vals_list: moves |= AccountMove.with_company(vals['company_id']).create(vals) # 4) Some moves might actually be refunds: convert them if the total amount is negative # We do this after the moves have been created since we need taxes, etc. to know if the total # is actually negative or not moves.filtered(lambda m: m.currency_id.round(m.amount_total) < 0).action_switch_invoice_into_refund_credit_note() return self.action_view_invoice_from_mr(moves) def action_view_invoice_from_mr(self, invoices=False): """This function returns an action that display existing vendor bills of given purchase order ids. When only one found, show the vendor bill immediately. """ if not invoices: # Invoice_ids may be filtered depending on the user. To ensure we get all # invoices related to the purchase order, we read them in sudo to fill the # cache. self.sudo()._read(['invoice_ids']) invoices = self.invoice_ids result = self.env['ir.actions.act_window']._for_xml_id('account.action_move_in_invoice_type') # choose the view_mode accordingly if len(invoices) > 1: result['domain'] = [('id', 'in', invoices.ids)] elif len(invoices) == 1: res = self.env.ref('account.view_move_form', False) form_view = [(res and res.id or False, 'form')] if 'views' in result: result['views'] = form_view + [(state, view) for state, view in result['views'] if view != 'form'] else: result['views'] = form_view result['res_id'] = invoices.id else: result = {'type': 'ir.actions.act_window_close'} return result @api.onchange('date_doc_kirim') def update_date_doc_kirim_so(self): if not self.sale_id: return self.sale_id.date_doc_kirim = self.date_doc_kirim def action_assign(self): res = super(StockPicking, self).action_assign() self.real_shipping_id = self.sale_id.real_shipping_id return res def ask_approval(self): if self.env.user.is_accounting: raise UserError("Bisa langsung Validate") # for calendar distribute only # if self.is_internal_use: # stock_move_lines = self.env['stock.move.line'].search([ # ('picking_id', '!=', False), # ('product_id', '=', 236805), # ('picking_id.partner_id', '=', self.partner_id.id), # ('qty_done', '>', 0), # ]) # list_state = ['confirmed', 'done'] # for stock_move_line in stock_move_lines: # if stock_move_line.picking_id.state not in list_state: # continue # raise UserError('Sudah pernah dikirim kalender') for pick in self: if not pick.is_internal_use: raise UserError("Selain Internal Use bisa langsung Validate") for line in pick.move_line_ids_without_package: if line.qty_done <= 0: raise UserError("Qty tidak boleh 0") pick.approval_status = 'pengajuan1' def ask_receipt_approval(self): if self.env.user.is_logistic_approver: raise UserError('Bisa langsung validate tanpa Ask Receipt') else: self.approval_receipt_status = 'pengajuan1' def ask_return_approval(self): for pick in self: if self.env.user.is_accounting: pick.approval_return_status = 'approved' else: pick.approval_return_status = 'pengajuan1' def calculate_line_no(self): for picking in self: name = picking.group_id.name for move in picking.move_ids_without_package: if picking.group_id.sale_id: order = self.env['sale.order'].search([('name', '=', name)], limit=1) else: order = self.env['purchase.order'].search([('name', '=', name)], limit=1) order_lines = order.order_line set_line = 0 for order_line in order_lines: if move.product_id == order_line.product_id: set_line = order_line.line_no break else: continue move.line_no = set_line for line in move.move_line_ids: line.line_no = set_line def _compute_summary_qty(self): sum_qty_detail = sum_qty_operation = count_line_detail = count_line_operation = 0 for picking in self: for detail in picking.move_line_ids_without_package: # detailed operations sum_qty_detail += detail.qty_done count_line_detail += 1 for operation in picking.move_ids_without_package: # operations sum_qty_operation += operation.product_uom_qty count_line_operation += 1 picking.summary_qty_detail = sum_qty_detail picking.count_line_detail = count_line_detail picking.summary_qty_operation = sum_qty_operation picking.count_line_operation = count_line_operation @api.onchange('picking_type_id') def _onchange_operation_type(self): self.is_internal_use = self.picking_type_id.is_internal_use return def button_validate(self): if self._name != 'stock.picking': return super(StockPicking, self).button_validate() if not self.picking_code: self.picking_code = self.env['ir.sequence'].next_by_code('stock.picking.code') or '0' if self.picking_type_id.code == 'incoming' and self.group_id.id == False and self.is_internal_use == False: raise UserError(_('Tidak bisa Validate jika tidak dari Document SO / PO')) if self.is_internal_use and not self.env.user.is_accounting: raise UserError("Harus di Approve oleh Accounting") if self.picking_type_id.id == 28 and not self.env.user.is_logistic_approver: raise UserError("Harus di Approve oleh Logistik") if self.group_id.sale_id: if self.group_id.sale_id.payment_link_midtrans: if self.group_id.sale_id.payment_status != 'settlement': raise UserError('Uang belum masuk (settlement), mohon konfirmasi ke sales atau finance') # for distribute calendar only # if self.is_internal_use: # stock_move_lines = self.env['stock.move.line'].search([ # ('picking_id', '!=', False), # ('product_id', '=', 236805), # ('picking_id.partner_id', '=', self.partner_id.id), # ('qty_done', '>', 0), # ]) # list_state = ['confirmed', 'done'] # for stock_move_line in stock_move_lines: # if stock_move_line.picking_id.state not in list_state: # continue # raise UserError('Sudah pernah dikirim kalender') # if self.picking_type_id.code == 'outgoing': # for line in self.move_line_ids_without_package: # if line.move_id.sale_line_id.qty_delivered + line.qty_done > line.move_id.sale_line_id.product_uom_qty: # raise UserError("Qty Delivered akan lebih dari Qty SO") # elif self.picking_type_id.code == 'incoming': # for line in self.move_ids_without_package: # if line.purchase_line_id.qty_received + line.quantity_done > line.purchase_line_id.product_qty: # raise UserError('Qty Received akan lebih dari Qty PO') if self.is_internal_use: self.approval_status = 'approved' elif self.picking_type_id.code == 'incoming': self.approval_receipt_status = 'approved' res = super(StockPicking, self).button_validate() self.calculate_line_no() return res @api.model def create(self, vals): self._use_faktur(vals) return super(StockPicking, self).create(vals) def write(self, vals): self._use_faktur(vals) return super(StockPicking, self).write(vals) def _use_faktur(self, vals): if vals.get('efaktur_id', False): self.env['vit.efaktur'].search( [ ('id', '=', vals['efaktur_id']) ], limit=1 ).is_used = True if self.efaktur_id.id != vals['efaktur_id']: self.efaktur_id.is_used = False return True def compute_delivery_status(self): for picking in self: if not picking.driver_departure_date and picking.picking_code: picking.delivery_status = "Sedang Dikemas" elif picking.driver_departure_date and not picking.driver_arrival_date: picking.delivery_status = "Dalam Perjalanan" elif picking.driver_departure_date and picking.driver_arrival_date and picking.carrier_id == 1: picking.delivery_status = "Diterima Konsumen" elif picking.driver_departure_date and picking.driver_arrival_date and picking.carrier_id != 1: picking.delivery_status = "Diterima Ekspedisi" else: picking.delivery_status = "Diterima Konsumen" def create_manifest_data(self, description, object): datetime_str = '' if isinstance(object, datetime.datetime): jakarta_timezone = pytz.timezone('Asia/Jakarta') datetime_str = object.replace(tzinfo=pytz.utc).astimezone(jakarta_timezone).strftime('%Y-%m-%d %H:%M:%S') return { 'description': description, 'datetime': datetime_str } def get_manifests(self): manifest_datas = [] departure_date = self.driver_departure_date arrival_date = self.driver_arrival_date if self.carrier_id.id == 32: # Pickup Bandengan manifest_datas.append(self.create_manifest_data('Sedang disiapkan', self.create_date)) if departure_date: manifest_datas.append(self.create_manifest_data('Siap diambil', departure_date)) if arrival_date: manifest_datas.append(self.create_manifest_data('Sudah diambil', arrival_date)) else: manifest_datas.append(self.create_manifest_data('Menunggu pickup', self.create_date)) if departure_date: manifest_datas.append(self.create_manifest_data('Sedang dikirim', departure_date)) if arrival_date: manifest_datas.append(self.create_manifest_data('Sudah sampai', arrival_date)) return manifest_datas def get_tracking_detail(self): self.ensure_one() response = { 'delivery_order': { 'name': self.name, 'carrier': self.carrier_id.name or '', 'receiver_name': '', 'receiver_city': '' }, 'delivered': False, 'waybill_number': self.delivery_tracking_no or '', 'delivery_status': None } if not self.waybill_id or len(self.waybill_id.manifest_ids) == 0: response['delivered'] = self.driver_arrival_date != False response['manifests'] = self.get_manifests() return response response['delivery_order']['receiver_name'] = self.waybill_id.receiver_name, response['delivery_order']['receiver_city'] = self.waybill_id.receiver_city, response['delivery_status'] = self.waybill_id._get_history('delivery_status') response['delivered'] = self.waybill_id.delivered response['manifests'] = [self.create_manifest_data(x.description, x.datetime) for x in self.waybill_id.manifest_ids] return response