From 7fdf8b3eb8cf42c7223039267faa4d22ba0ba334 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 2 Jan 2026 19:29:12 +0700 Subject: push multiple cancel picking and template cancel picking --- fixco_custom/__manifest__.py | 1 + fixco_custom/models/__init__.py | 3 +- fixco_custom/models/stock_picking.py | 17 ++- fixco_custom/models/upload_cancel_picking.py | 213 +++++++++++++++++++++++++++ fixco_custom/security/ir.model.access.csv | 2 + fixco_custom/views/ir_sequence.xml | 11 ++ fixco_custom/views/stock_picking.xml | 10 ++ fixco_custom/views/upload_cancel_picking.xml | 61 ++++++++ 8 files changed, 316 insertions(+), 2 deletions(-) create mode 100644 fixco_custom/models/upload_cancel_picking.py create mode 100644 fixco_custom/views/upload_cancel_picking.xml diff --git a/fixco_custom/__manifest__.py b/fixco_custom/__manifest__.py index 7a796ad..aef74df 100755 --- a/fixco_custom/__manifest__.py +++ b/fixco_custom/__manifest__.py @@ -48,6 +48,7 @@ 'views/vit_kota.xml', 'views/token_log.xml', 'views/wizard_purchase_pricelist.xml', + 'views/upload_cancel_picking.xml', ], 'demo': [], 'css': [], diff --git a/fixco_custom/models/__init__.py b/fixco_custom/models/__init__.py index 37c8b45..393efeb 100755 --- a/fixco_custom/models/__init__.py +++ b/fixco_custom/models/__init__.py @@ -34,4 +34,5 @@ from . import coretax_faktur from . import token_log from . import purchase_pricelist_wizard from . import stock_picking_return -from . import account_move_reversal \ No newline at end of file +from . import account_move_reversal +from . import upload_cancel_picking \ No newline at end of file diff --git a/fixco_custom/models/stock_picking.py b/fixco_custom/models/stock_picking.py index 62f3d3b..4b4a850 100755 --- a/fixco_custom/models/stock_picking.py +++ b/fixco_custom/models/stock_picking.py @@ -66,12 +66,27 @@ class StockPicking(models.Model): type_sku = fields.Selection([('single', 'Single SKU'), ('multi', 'Multi SKU')], string='Type SKU') list_product = fields.Char(string='List Product') + def action_cancel_selected_pickings(self): + for picking in self: + if picking.state == 'done': + raise UserError( + _("Picking %s sudah DONE dan tidak bisa di-cancel.") % picking.name + ) + + if picking.state == 'assigned': + picking.do_unreserve() + + picking.action_cancel() + + return None + + def rts_ginee(self): self.get_shipping_parameter() self.ship_order() - def create_invoices(self): + so_id = self.sale_id.id if not so_id: raise UserError(_("Gaada So nya!")) diff --git a/fixco_custom/models/upload_cancel_picking.py b/fixco_custom/models/upload_cancel_picking.py new file mode 100644 index 0000000..a42ef1d --- /dev/null +++ b/fixco_custom/models/upload_cancel_picking.py @@ -0,0 +1,213 @@ +from odoo import models, fields, api, _ +from datetime import datetime +import base64 +import xlrd +from odoo.exceptions import ValidationError + + +class UploadCancelPicking(models.Model): + _name = "upload.cancel.picking" + _description = "Upload Cancel Picking" + _order = "create_date desc" + _rec_name = "number" + + picking_lines = fields.One2many( + 'upload.cancel.picking.line', + 'upload_cancel_picking_id', + string='Lines', + copy=False + ) + number = fields.Char('Number', copy=False) + date_upload = fields.Datetime('Cancel Date', copy=False) + user_id = fields.Many2one( + 'res.users', + 'Created By', + default=lambda self: self.env.user + ) + excel_file = fields.Binary('Excel File', attachment=True) + filename = fields.Char('File Name') + + @api.model + def create(self, vals): + vals['number'] = self.env['ir.sequence'].next_by_code( + 'upload.cancel.picking' + ) or '/' + return super().create(vals) + + def action_import_excel(self): + self.ensure_one() + + if not self.excel_file: + raise ValidationError(_("Please upload an Excel file first.")) + + # === Load Excel === + try: + file_content = base64.b64decode(self.excel_file) + workbook = xlrd.open_workbook(file_contents=file_content) + sheet = workbook.sheet_by_index(0) + except Exception: + raise ValidationError(_("Invalid Excel file format.")) + + # === Validate Header === + header = [ + str(sheet.cell(0, col).value).strip().lower() + for col in range(sheet.ncols) + ] + + if 'invoice' not in header: + raise ValidationError( + _("Invalid Excel format. Expected column: Invoice") + ) + + invoice_col = header.index('invoice') + + # === Read Rows === + rows_data = [] + for row_idx in range(1, sheet.nrows): + invoice_marketplace = str( + sheet.cell(row_idx, invoice_col).value + ).strip() + + if not invoice_marketplace: + raise ValidationError( + _("Invoice kosong di baris Excel %s") % (row_idx + 1) + ) + + rows_data.append((row_idx + 1, invoice_marketplace)) + + if not rows_data: + raise ValidationError(_("Excel tidak berisi data.")) + + # === Validate Duplicate in Excel === + seen = set() + duplicate_excel_rows = [] + + for row_num, invoice_marketplace in rows_data: + if invoice_marketplace in seen: + duplicate_excel_rows.append(str(row_num)) + seen.add(invoice_marketplace) + + if duplicate_excel_rows: + raise ValidationError( + _("Duplicate Invoice di file Excel pada baris: %s") + % ", ".join(duplicate_excel_rows) + ) + + # === Validate Duplicate in System === + invoice_to_check = [inv for _, inv in rows_data] + existing_invoices = set() + + chunk_size = 500 + for i in range(0, len(invoice_to_check), chunk_size): + chunk = invoice_to_check[i:i + chunk_size] + records = self.env['upload.cancel.picking.line'].search([ + ('invoice_marketplace', 'in', chunk) + ]) + existing_invoices.update(records.mapped('invoice_marketplace')) + + duplicate_system_rows = [] + for row_num, invoice_marketplace in rows_data: + if invoice_marketplace in existing_invoices: + duplicate_system_rows.append(str(row_num)) + + if duplicate_system_rows: + raise ValidationError( + _("Invoice Marketplace sudah ada di sistem. " + "Ditemukan di baris: %s") + % ", ".join(duplicate_system_rows) + ) + + # === Create Lines === + line_vals = [ + (0, 0, { + 'invoice_marketplace': invoice_marketplace, + 'upload_cancel_picking_id': self.id, + }) + for _, invoice_marketplace in rows_data + ] + + # Clear old lines (safe way) + self.picking_lines = [(5, 0, 0)] + self.write({'picking_lines': line_vals}) + self.picking_lines.get_order_id() + + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'title': _('Success'), + 'message': _('Imported %s lines from Excel.') + % len(line_vals), + 'sticky': False, + 'next': {'type': 'ir.actions.act_window_close'}, + } + } + + def action_cancel_picking(self): + self.date_upload = datetime.utcnow() + self.picking_lines.cancel_picking() + + +class UploadCancelPickingLine(models.Model): + _name = "upload.cancel.picking.line" + _description = "Upload Cancel Picking Line" + _inherit = ['mail.thread'] + + upload_cancel_picking_id = fields.Many2one( + 'upload.cancel.picking', + string='Upload' + ) + invoice_marketplace = fields.Char( + 'Invoice Marketplace', + required=True + ) + picking_id = fields.Many2one( + 'stock.picking', + 'Picking Reference' + ) + status_picking = fields.Selection([ + ('done', 'Done'), + ('cancel', 'Cancel'), + ('assigned', 'Ready'), + ('confirmed', 'Waiting'), + ('waiting', 'Waiting Another Operation'), + ], related='picking_id.state') + message_error = fields.Text('Error Message') + is_grouped = fields.Boolean('Is Grouped', default=False) + group_key = fields.Char('Group Key') + + + def get_order_id(self): + StockPicking = self.env['stock.picking'] + + invoices = self.mapped('invoice_marketplace') + + if not invoices: + return + + # Ambil semua picking yang matching invoice_mp + pickings = StockPicking.search([ + ('invoice_mp', 'in', invoices) + ]) + + picking_map = { + p.invoice_mp: p.id + for p in pickings + if p.invoice_mp + } + + for line in self: + picking_id = picking_map.get(line.invoice_marketplace) + + if picking_id: + line.picking_id = picking_id + line.message_error = False + else: + line.picking_id = False + line.message_error = _( + "Stock Picking tidak ditemukan untuk invoice %s" + ) % line.invoice_marketplace + + def cancel_picking(self): + for line in self: + line.picking_id.action_cancel() \ No newline at end of file diff --git a/fixco_custom/security/ir.model.access.csv b/fixco_custom/security/ir.model.access.csv index f51c8ad..7795156 100755 --- a/fixco_custom/security/ir.model.access.csv +++ b/fixco_custom/security/ir.model.access.csv @@ -42,3 +42,5 @@ access_token_log,access.token.log,model_token_log,,1,1,1,1 access_purchase_pricelist_wizard,purchase.pricelist.wizard,model_purchase_pricelist_wizard,,1,1,1,1 access_stock_return_picking,stock.return.picking,model_stock_return_picking,,1,1,1,1 access_stock_return_picking_line,stock.return.picking.line,model_stock_return_picking_line,,1,1,1,1 +access_upload_cancel_picking,access.upload.cancel.picking,model_upload_cancel_picking,,1,1,1,1 +access_upload_cancel_picking_line,access.upload.cancel.picking.line,model_upload_cancel_picking_line,,1,1,1,1 diff --git a/fixco_custom/views/ir_sequence.xml b/fixco_custom/views/ir_sequence.xml index 06e11cb..e4845f7 100644 --- a/fixco_custom/views/ir_sequence.xml +++ b/fixco_custom/views/ir_sequence.xml @@ -1,6 +1,17 @@ + + Upload Cancel Picking + upload.cancel.picking + TRUE + UCP/%(year)s/ + 5 + 1 + 1 + 4 + + Upload Payments upload.payments diff --git a/fixco_custom/views/stock_picking.xml b/fixco_custom/views/stock_picking.xml index bb1cac8..5095333 100755 --- a/fixco_custom/views/stock_picking.xml +++ b/fixco_custom/views/stock_picking.xml @@ -120,6 +120,16 @@ + + Cancel Picking + + + code + + action = records.action_cancel_selected_pickings() + + + stock.picking.filter.name.extend stock.picking diff --git a/fixco_custom/views/upload_cancel_picking.xml b/fixco_custom/views/upload_cancel_picking.xml new file mode 100644 index 0000000..5f14a75 --- /dev/null +++ b/fixco_custom/views/upload_cancel_picking.xml @@ -0,0 +1,61 @@ + + + + + upload.cancel.picking.tree + upload.cancel.picking + + + + + + + + + + + upload.cancel.picking.form + upload.cancel.picking + +
+
+
+ + + + + + + + + + + + + + +
+
+
+ + + Upload Cancel Picking + ir.actions.act_window + upload.cancel.picking + tree,form + + + +
+
-- cgit v1.2.3