summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xindoteknik_custom/__manifest__.py1
-rwxr-xr-xindoteknik_custom/models/__init__.py1
-rw-r--r--indoteknik_custom/models/tukar_guling.py174
-rwxr-xr-xindoteknik_custom/security/ir.model.access.csv2
-rw-r--r--indoteknik_custom/views/tukar_guling.xml123
5 files changed, 301 insertions, 0 deletions
diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py
index 9fe3dcdb..e973f553 100755
--- a/indoteknik_custom/__manifest__.py
+++ b/indoteknik_custom/__manifest__.py
@@ -167,6 +167,7 @@
'views/coretax_faktur.xml',
'views/public_holiday.xml',
'views/stock_inventory.xml',
+ 'views/tukar_guling.xml',
],
'demo': [],
'css': [],
diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py
index 08fa9803..72bd7cee 100755
--- a/indoteknik_custom/models/__init__.py
+++ b/indoteknik_custom/models/__init__.py
@@ -150,3 +150,4 @@ from . import stock_backorder_confirmation
from . import account_payment_register
from . import stock_inventory
from . import approval_invoice_date
+from . import tukar_guling
diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py
new file mode 100644
index 00000000..b4a901d0
--- /dev/null
+++ b/indoteknik_custom/models/tukar_guling.py
@@ -0,0 +1,174 @@
+from odoo import models, fields, api
+from odoo.exceptions import UserError, ValidationError
+
+
+class TukarGuling(models.Model):
+ _name = 'tukar.guling'
+ _description = 'Tukar Guling'
+ _order = 'date desc, id desc'
+ _rec_name = 'name'
+
+ name = fields.Char('Number', required=True, copy=False, readonly=True, default='New')
+ date = fields.Datetime('Date', default=fields.Datetime.now, required=True)
+ out_num = fields.Many2one('stock.picking', 'Nomor BU/Out',
+ domain=[('picking_type_id.code', '=', 'outgoing')])
+ in_num = fields.Many2one('stock.picking', 'Nomor BU/In', domain=[('picking_type_id.code', '=', 'incoming')])
+ ba_num = fields.Text('Nomor BA')
+ notes = fields.Text('Notes')
+ return_type = fields.Selection(String='Return Type', selection=[
+ ('tukar_guling', 'Tukar Guling'), # -> barang yang sama
+ ('revisi_so', 'Revisi SO'), # -> ganti barang ?
+ ('revisi_po', 'Revisi PO'),
+ ('credit_memo', 'Credit Memo'), # -> dijadiin credit memo
+ ('debit_memo', 'Debit Memo')])
+ state = fields.Selection(string='Status', selection=[
+ ('draft', 'Draft'),
+ ('waiting', 'Waiting for Approval'),
+ ('done', 'Done'),
+ ('cancel', 'Canceled')
+ ], default='draft', tracking=True, required=True)
+
+ line_ids = fields.One2many('tukar.guling.line', 'tukar_guling_id', string='Product Lines')
+
+ @api.constrains('return_type', 'out_num', 'in_num')
+ def _check_return_type_fields(self):
+ for rec in self:
+ if rec.return_type in ['tukar_guling', 'credit_memo', 'revisi_so'] and not rec.out_num:
+ raise ValidationError("Field BU/Out wajib diisi untuk jenis return type tersebut.")
+ if rec.return_type in ['debit_memo', 'revisi_po'] and not rec.in_num:
+ raise ValidationError("Field BU/In wajib diisi untuk jenis return type tersebut.")
+
+ @api.constrains('line_ids', 'state')
+ def _check_product_lines(self):
+ """Constraint: Product lines harus ada jika state bukan draft"""
+ for record in self:
+ if record.state in ('waiting', 'done') and not record.line_ids:
+ raise ValidationError("Product lines harus diisi sebelum submit atau approve!")
+
+ def _validate_product_lines(self):
+ """Helper method untuk validasi product lines"""
+ self.ensure_one()
+
+
+ # Check ada product lines
+ if not self.line_ids:
+ raise UserError("Belum ada product lines yang ditambahkan!")
+
+ # Check product sudah diisi
+ empty_lines = self.line_ids.filtered(lambda line: not line.product_id)
+ if empty_lines:
+ raise UserError("Ada product lines yang belum diisi productnya!")
+
+ # Check quantity > 0
+ zero_qty_lines = self.line_ids.filtered(lambda line: line.product_uom_qty <= 0)
+ if zero_qty_lines:
+ raise UserError("Quantity product tidak boleh kosong atau 0!")
+
+ return True
+
+ @api.model_create_multi
+ def create(self, vals_list):
+ for vals in vals_list:
+ if vals.get('name', 'New') == 'New':
+ vals['name'] = self.env['ir.sequence'].next_by_code('tukar.guling') or 'PTG/00001'
+ return super(TukarGuling, self).create(vals_list)
+
+ def copy(self, default=None):
+ """Override copy untuk custom behavior saat duplicate"""
+ if default is None:
+ default = {}
+
+ # Reset fields penting saat duplicate
+ default.update({
+ 'name': 'New',
+ 'state': 'draft',
+ 'date': fields.Datetime.now(),
+ })
+
+ # Copy record dengan default values
+ new_record = super(TukarGuling, self).copy(default)
+
+ # Re-sequence line items record baru
+ if new_record.line_ids:
+ for i, line in enumerate(new_record.line_ids):
+ line.sequence = (i + 1) * 10
+
+ return new_record
+
+ def action_draft(self):
+ """Reset to draft state"""
+ for record in self:
+ if record.state == 'cancel':
+ record.write({'state': 'draft'})
+ else:
+ raise UserError("Hanya record yang di-cancel yang bisa dikembalikan ke draft")
+
+ def action_submit(self):
+ self.ensure_one()
+ # cek bu out sudah diisi atau blm
+ if not self.out_num:
+ raise UserError("BU/Out harus diisi!")
+
+ # cek return type
+ # if not self.return_type:
+ # raise UserError("Return Type harus diisi!")
+
+ if self.state != 'draft':
+ raise UserError("Hanya status Draft saja yang bisa di submit")
+ self.state = 'waiting'
+
+ def action_approve(self):
+ self.ensure_one()
+
+ # cek bu out sudah diisi atau blm
+ if not self.out_num:
+ raise UserError("BU/Out harus diisi!")
+
+ # cek return type
+ if not self.return_type:
+ raise UserError("Return Type harus diisi!")
+
+ if self.state != 'waiting':
+ raise UserError("Hanya status Waiting saja yang bisa di approve")
+ self.state = 'done'
+
+ def action_cancel(self):
+ self.ensure_one()
+ if self.state == 'done':
+ raise UserError("Tidak bisa cancel jika sudah done")
+ self.state = 'cancel'
+
+
+class TukarGulingLine(models.Model):
+ _name = 'tukar.guling.line'
+ _description = 'Tukar Guling Line'
+ _order = 'sequence, id'
+
+ sequence = fields.Integer('Sequence', default=10, copy=False)
+ tukar_guling_id = fields.Many2one('tukar.guling', string='Tukar Guling', required=True, ondelete='cascade')
+ product_id = fields.Many2one('product.product', string='Product', required=True)
+ product_uom_qty = fields.Float('Quantity', digits='Product Unit of Measure', required=True, default=1.0)
+ product_uom = fields.Many2one('uom.uom', string='Unit of Measure')
+ name = fields.Text('Description')
+
+ @api.model_create_multi
+ def create(self, vals_list):
+ """Override create to auto-assign sequence"""
+ for vals in vals_list:
+ if 'sequence' not in vals or vals.get('sequence', 0) <= 0:
+ # Get max sequence untuk tukar_guling yang sama
+ tukar_guling_id = vals.get('tukar_guling_id')
+ if tukar_guling_id:
+ max_seq = self.search([
+ ('tukar_guling_id', '=', tukar_guling_id)
+ ], order='sequence desc', limit=1)
+ vals['sequence'] = (max_seq.sequence or 0) + 10
+ else:
+ vals['sequence'] = 10
+ return super(TukarGulingLine, self).create(vals_list)
+
+ @api.onchange('product_id')
+ def _onchange_product_id(self):
+ if self.product_id:
+ self.name = self.product_id.display_name
+ self.product_uom = self.product_id.uom_id \ No newline at end of file
diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv
index 601f04c5..26198a0f 100755
--- a/indoteknik_custom/security/ir.model.access.csv
+++ b/indoteknik_custom/security/ir.model.access.csv
@@ -181,3 +181,5 @@ access_shipping_option,shipping.option,model_shipping_option,,1,1,1,1
access_production_purchase_match,access.production.purchase.match,model_production_purchase_match,,1,1,1,1
access_image_carousel,access.image.carousel,model_image_carousel,,1,1,1,1
access_v_sale_notin_matchpo,access.v.sale.notin.matchpo,model_v_sale_notin_matchpo,,1,1,1,1
+access_tukar_guling_all_users,tukar.guling.all.users,model_tukar_guling,base.group_user,1,1,1,1
+access_tukar_guling_line_all_users,tukar.guling.line.all.users,model_tukar_guling_line,base.group_user,1,1,1,1
diff --git a/indoteknik_custom/views/tukar_guling.xml b/indoteknik_custom/views/tukar_guling.xml
new file mode 100644
index 00000000..9b490cd0
--- /dev/null
+++ b/indoteknik_custom/views/tukar_guling.xml
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<odoo>
+ <data>
+ <!-- Action -->
+ <record id="action_pengajuan_tukar_guling" model="ir.actions.act_window">
+ <field name="name">Pengajuan Tukar Guling</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">tukar.guling</field>
+ <field name="view_mode">tree,form</field>
+ </record>
+
+ <!-- Menu -->
+ <menuitem
+ id="menu_pengajuan_tukar_guling"
+ name="Pengajuan Tukar Guling"
+ parent="stock.menu_warehouse_report"
+ sequence="3"
+ action="action_pengajuan_tukar_guling"
+ />
+
+ <!-- Sequence -->
+ <record id="seq_tukar_guling" model="ir.sequence">
+ <field name="name">Pengajuan Tukar Guling</field>
+ <field name="code">tukar.guling</field>
+ <field name="prefix">PTG/</field>
+ <field name="padding">5</field>
+ <field name="number_next">1</field>
+ <field name="number_increment">1</field>
+ <field name="company_id" eval="False"/>
+ </record>
+
+ <!-- Tree View -->
+ <record id="pengajuan_tukar_guling_tree" model="ir.ui.view">
+ <field name="name">pengajuan.tukar.guling.tree</field>
+ <field name="model">tukar.guling</field>
+ <field name="arch" type="xml">
+ <tree create="1" delete="1" default_order="create_date desc">
+ <field name="name"/>
+ <field name="date"/>
+ <field name="out_num" string="BU/Out"/>
+ <field name="in_num" string="BU/In"/>
+ <field name="ba_num" string="Nomor BA"/>
+ <field name="return_type" string="Return Type"/>
+ <field name="state" widget="badge"
+ decoration-info="state in ('draft', 'waiting')"
+ decoration-success="state == 'done'"
+ decoration-muted="state == 'cancel'"
+ />
+ </tree>
+ </field>
+ </record>
+
+ <!-- Form View -->
+ <record id="pengajuan_tukar_guling_form" model="ir.ui.view">
+ <field name="name">pengajuan.tukar.guling.form</field>
+ <field name="model">tukar.guling</field>
+ <field name="arch" type="xml">
+ <form>
+ <header>
+ <button name="action_submit" string="Submit" type="object"
+ class="btn-primary"
+ attrs="{'invisible': [('state', '!=', 'draft')]}"/>
+ <button name="action_approve" string="Approve" type="object"
+ class="btn-primary"
+ attrs="{'invisible': [('state', '!=', 'waiting')]}"/>
+ <button name="action_cancel" string="Cancel" type="object"
+ class="btn-secondary"
+ attrs="{'invisible': [('state', 'in', ('done', 'cancel'))]}"
+ confirm="Are you sure you want to cancel this record?"/>
+ <button name="action_draft" string="Set to Draft" type="object"
+ class="btn-secondary"
+ attrs="{'invisible': [('state', '!=', 'cancel')]}"
+ confirm="Are you sure you want to reset this record to draft?"/>
+ <field name="state" widget="statusbar" readonly="1"/>
+ </header>
+ <sheet>
+ <div class="oe_title">
+ <h1>
+ <field name="name" readonly="1" class="oe_inline"/>
+ </h1>
+ <hr/>
+ </div>
+ <group>
+ <group>
+ <field name="date" string="Date" readonly="1"/>
+ <field name="out_num" string="BU/Out"
+ attrs="{'invisible': [('return_type', 'not in', ['tukar_guling', 'credit_memo', 'revisi_so'])],
+ 'required': [('return_type', 'in', ['tukar_guling', 'credit_memo', 'revisi_so'])]}"/>
+
+ <field name="in_num" string="BU/In"
+ attrs="{'invisible': [('return_type', 'not in', ['debit_memo', 'revisi_po'])],
+ 'required': [('return_type', 'in', ['debit_memo', 'revisi_po'])]}"/>
+ <field name="ba_num" string="Nomor BA"/>
+ </group>
+ <group>
+ <field name="return_type"/>
+ <field name="notes"/>
+<!-- <field name="state" readonly="1"/>-->
+ </group>
+ </group>
+
+ <!-- Product Lines -->
+ <notebook>
+ <page string="Product Lines" name="product_lines">
+ <field name="line_ids">
+ <tree string="Product Lines" editable="bottom">
+ <field name="sequence" widget="handle"/>
+ <field name="product_id" required="1"
+ options="{'no_create': True, 'no_create_edit': True}"/>
+ <field name="name" force_save="1"/>
+ <field name="product_uom_qty" string="Quantity"/>
+ <field name="product_uom" string="UoM"
+ options="{'no_create': True, 'no_create_edit': True}"/>
+ </tree>
+ </field>
+ </page>
+ </notebook>
+ </sheet>
+ </form>
+ </field>
+ </record>
+ </data>
+</odoo> \ No newline at end of file