diff options
| author | Azka Nathan <darizkyfaz@gmail.com> | 2026-01-02 19:29:12 +0700 |
|---|---|---|
| committer | Azka Nathan <darizkyfaz@gmail.com> | 2026-01-02 19:29:12 +0700 |
| commit | 7fdf8b3eb8cf42c7223039267faa4d22ba0ba334 (patch) | |
| tree | bd936109622ce96889cb30c93c15c8c4c78927ca | |
| parent | 29311a36f0c39c12272e868577004b3d7bac58d6 (diff) | |
push multiple cancel picking and template cancel picking
| -rwxr-xr-x | fixco_custom/__manifest__.py | 1 | ||||
| -rwxr-xr-x | fixco_custom/models/__init__.py | 3 | ||||
| -rwxr-xr-x | fixco_custom/models/stock_picking.py | 17 | ||||
| -rw-r--r-- | fixco_custom/models/upload_cancel_picking.py | 213 | ||||
| -rwxr-xr-x | fixco_custom/security/ir.model.access.csv | 2 | ||||
| -rw-r--r-- | fixco_custom/views/ir_sequence.xml | 11 | ||||
| -rwxr-xr-x | fixco_custom/views/stock_picking.xml | 10 | ||||
| -rw-r--r-- | fixco_custom/views/upload_cancel_picking.xml | 61 |
8 files changed, 316 insertions, 2 deletions
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 @@ <?xml version="1.0" encoding="UTF-8" ?> <odoo> <data noupdate="0"> + <record id="sequence_upload_cancel_picking" model="ir.sequence"> + <field name="name">Upload Cancel Picking</field> + <field name="code">upload.cancel.picking</field> + <field name="active">TRUE</field> + <field name="prefix">UCP/%(year)s/</field> + <field name="padding">5</field> + <field name="number_next">1</field> + <field name="number_increment">1</field> + <field name="company_id">4</field> + </record> + <record id="sequence_upload_payments" model="ir.sequence"> <field name="name">Upload Payments</field> <field name="code">upload.payments</field> 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 @@ </field> </record> + <record id="action_cancel_selected_pickings" model="ir.actions.server"> + <field name="name">Cancel Picking</field> + <field name="model_id" ref="stock.model_stock_picking"/> + <field name="binding_model_id" ref="stock.model_stock_picking"/> + <field name="state">code</field> + <field name="code"> + action = records.action_cancel_selected_pickings() + </field> + </record> + <record id="view_stock_picking_filter_inherit_name" model="ir.ui.view"> <field name="name">stock.picking.filter.name.extend</field> <field name="model">stock.picking</field> 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<odoo> + <data> + <record id="upload_cancel_picking_tree" model="ir.ui.view"> + <field name="name">upload.cancel.picking.tree</field> + <field name="model">upload.cancel.picking</field> + <field name="arch" type="xml"> + <tree default_order="create_date desc"> + <field name="number"/> + <field name="date_upload"/> + <field name="user_id"/> + </tree> + </field> + </record> + + <record id="view_upload_cancel_picking_form" model="ir.ui.view"> + <field name="name">upload.cancel.picking.form</field> + <field name="model">upload.cancel.picking</field> + <field name="arch" type="xml"> + <form string="Upload Cancel Picking"> + <header> + <button name="action_import_excel" string="Import Excel" type="object" class="oe_highlight" attrs="{'invisible': [('number', '=', False)]}"/> + <button name="action_cancel_picking" string="Cancel Picking" type="object" class="oe_highlight" attrs="{'invisible': [('number', '=', False)]}"/> + <field name="number" widget="field_no_edit" options="{'no_open': True}"/> + <field name="date_upload"/> + <field name="user_id" widget="field_no_edit" options="{'no_open': True}"/> + </header> + <sheet> + <group> + <field name="excel_file" filename="filename"/> + <field name="filename" invisible="1"/> + </group> + <field name="picking_lines"> + <tree editable="bottom"> + <field name="invoice_marketplace"/> + <field name="picking_id"/> + <field name="status_picking"/> + <field name="message_error"/> + </tree> + </field> + </sheet> + </form> + </field> + </record> + + <record id="upload_cancel_picking_action" model="ir.actions.act_window"> + <field name="name">Upload Cancel Picking</field> + <field name="type">ir.actions.act_window</field> + <field name="res_model">upload.cancel.picking</field> + <field name="view_mode">tree,form</field> + </record> + + <menuitem + id="menu_upload_cancel_picking" + name="Upload Cancel Picking" + parent="sale.menu_sale_report" + sequence="4" + action="upload_cancel_picking_action" + /> + </data> +</odoo> |
