summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xfixco_custom/__manifest__.py1
-rwxr-xr-xfixco_custom/models/__init__.py3
-rwxr-xr-xfixco_custom/models/stock_picking.py17
-rw-r--r--fixco_custom/models/upload_cancel_picking.py213
-rwxr-xr-xfixco_custom/security/ir.model.access.csv2
-rw-r--r--fixco_custom/views/ir_sequence.xml11
-rwxr-xr-xfixco_custom/views/stock_picking.xml10
-rw-r--r--fixco_custom/views/upload_cancel_picking.xml61
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>