diff options
| -rwxr-xr-x | indoteknik_custom/__manifest__.py | 1 | ||||
| -rw-r--r-- | indoteknik_custom/models/tukar_guling.py | 211 | ||||
| -rwxr-xr-x | indoteknik_custom/security/ir.model.access.csv | 1 | ||||
| -rw-r--r-- | indoteknik_custom/views/tukar_guling.xml | 196 | ||||
| -rw-r--r-- | indoteknik_custom/views/tukar_guling_po.xml | 117 |
5 files changed, 357 insertions, 169 deletions
diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py index e973f553..9f8fad01 100755 --- a/indoteknik_custom/__manifest__.py +++ b/indoteknik_custom/__manifest__.py @@ -168,6 +168,7 @@ 'views/public_holiday.xml', 'views/stock_inventory.xml', 'views/tukar_guling.xml', + 'views/tukar_guling_po.xml', ], 'demo': [], 'css': [], diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index f8cbec0a..95aa7cd6 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -12,66 +12,34 @@ class TukarGuling(models.Model): 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')]) + ('credit_memo', 'Credit Memo')]) # -> dijadiin credit memo state = fields.Selection(string='Status', selection=[ ('draft', 'Draft'), - ('waiting', 'Waiting for Approval'), + ('approval_sales', ' Approval Sales'), + ('approval_logistic', 'Approval Logistic'), + ('approval_finance', 'Approval Finance'), ('done', 'Done'), ('cancel', 'Canceled') ], default='draft', tracking=True, required=True) line_ids = fields.One2many('tukar.guling.line', 'tukar_guling_id', string='Product Lines') - @api.onchange('return_type') - def _onchange_return_type(self): - in_domain = [] - out_domain = [] - - if self.return_type in ['debit_memo', 'revisi_po']: - # Hanya tampilkan BU In - in_domain = [('picking_type_id.code', '=', 'incoming')] - out_domain = [('id', '=', False)] # Kosongkan BU Out - elif self.return_type in ['revisi_so', 'credit_memo']: - # Hanya tampilkan BU Out - in_domain = [('id', '=', False)] # Kosongkan BU In - out_domain = [('picking_type_id.code', '=', 'outgoing')] - elif self.return_type == 'tukar_guling': - # Boleh pilih keduanya - in_domain = [('picking_type_id.code', '=', 'incoming')] - out_domain = [('picking_type_id.code', '=', 'outgoing')] - - return { - 'domain': { - 'in_num': in_domain, - 'out_num': out_domain, - } - } - - @api.constrains('return_type', 'in_num', 'out_num') + @api.constrains('return_type', 'out_num') def _check_required_bu_fields(self): for record in self: - if record.return_type in ['debit_memo', 'revisi_po'] and not record.in_num: - raise ValidationError("BU/In harus diisi untuk return type Debit Memo atau Revisi PO.") - - if record.return_type in ['revisi_so', 'credit_memo'] and not record.out_num: - raise ValidationError("BU/Out harus diisi untuk return type Revisi SO atau Credit Memo.") - - if record.return_type == 'tukar_guling' and not (record.in_num or record.out_num): - raise ValidationError("Untuk Tukar Guling, minimal isi salah satu, BU/In atau BU/Out.") + if record.return_type in ['revisi_so', 'credit_memo', 'tukar_guling'] and not record.out_num: + raise ValidationError("BU/Out harus diisi!") @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: + if record.state in ('approval_sales', 'approval_logistic', 'approval_finance', 'done') and not record.line_ids: raise ValidationError("Product lines harus diisi sebelum submit atau approve!") def _validate_product_lines(self): @@ -95,29 +63,29 @@ class TukarGuling(models.Model): 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) + @api.model + def create(self, vals): + if not vals.get('name') or vals['name'] == 'New': + vals['name'] = self.env['ir.sequence'].next_by_code('tukar.guling') or 'New' + return super(TukarGuling, self).create(vals) def copy(self, default=None): - """Override copy untuk custom behavior saat duplicate""" if default is None: default = {} - # Reset fields penting saat duplicate + if 'name' not in default: + default.update({ + 'name': self.env['ir.sequence'].next_by_code(self._name) or 'New', + }) + 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 + # Re-sequence lines if new_record.line_ids: for i, line in enumerate(new_record.line_ids): line.sequence = (i + 1) * 10 @@ -134,40 +102,153 @@ class TukarGuling(models.Model): def action_submit(self): self.ensure_one() - # cek bu out sudah diisi atau blm + + if self.state != 'draft': + raise UserError("Submit hanya bisa dilakukan dari Draft.") + self.state = 'approval_sales' + + def action_approve(self): + self.ensure_one() + 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 not self.return_type: + raise UserError("Return Type harus diisi!") + + # Cek hak akses berdasarkan state + if self.state == 'approval_sales': + if not self.env.user.has_group('indoteknik_custom.group_sales_manager'): + raise UserError("Hanya Sales Manager yang boleh approve tahap ini.") + self.state = 'approval_logistic' + + elif self.state == 'approval_logistic': + if not self.env.user.has_group('indoteknik_custom.group_logistic'): + raise UserError("Hanya Logistic Manager yang boleh approve tahap ini.") + self.state = 'approval_finance' + + elif self.state == 'approval_finance': + if not self.env.user.has_group('indoteknik_custom.group_finance'): + raise UserError("Hanya Finance Manager yang boleh approve tahap ini.") + self.state = 'done' + + else: + raise UserError("Status ini tidak bisa di-approve.") + def action_cancel(self): + self.ensure_one() + # if self.state == 'done': + # raise UserError("Tidak bisa cancel jika sudah done") + self.state = 'cancel' + +class TukarGulingPO(models.Model): + _name = 'tukar.guling.po' + _inherit = 'tukar.guling' + _description = 'Tukar Guling PO' + + # tukar_guling_id = fields.Many2one( + # 'tukar.guling', required=True, ondelete='cascade', string='Tukar Guling Ref' + # ) + + return_type = fields.Selection([ + ('tukar_guling', 'Tukar Guling'), + ('revisi_po', 'Revisi PO'), + ('debit_memo', 'Debit Memo'), + ], string='Return Type', required=True) + + @api.constrains('return_type', 'out_num') + def _check_required_bu_fields(self): + for record in self: + if record.return_type in ['tukar_guling', 'revisi_po', 'debit_memo'] and not record.out_num: + raise ValidationError("BU/Out harus diisi!") + + @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 ('approval_sales', 'approval_logistic', 'approval_finance', '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 + def create(self, vals): + if not vals.get('name') or vals['name'] in ('New', False): + vals['name'] = self.env['ir.sequence'].next_by_code('tukar.guling.po') or 'New' + return super(TukarGulingPO, self).create(vals) + def copy(self, default=None): + if default is None: + default = {} + + # Generate sequence satu-satunya di sini + default['name'] = self.env['ir.sequence'].next_by_code('tukar.guling.po') or 'New' + default['state'] = 'draft' + default['date'] = fields.Datetime.now() + + new_record = super(TukarGulingPO, self).copy(default) + + # Re-sequence lines + 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() if self.state != 'draft': - raise UserError("Hanya status Draft saja yang bisa di submit") - self.state = 'waiting' + raise UserError("Submit hanya bisa dilakukan dari Draft.") + self.state = 'approval_sales' 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' + if self.state == 'approval_sales': + self.state = 'approval_logistic' + elif self.state == 'approval_logistic': + self.state = 'approval_finance' + elif self.state == 'approval_finance': + self.state = 'done' + else: + raise UserError("Status ini tidak bisa di-approve.") def action_cancel(self): self.ensure_one() - if self.state == 'done': - raise UserError("Tidak bisa cancel jika sudah done") + # 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' diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv index 26198a0f..f8fb6ac6 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -182,4 +182,5 @@ access_production_purchase_match,access.production.purchase.match,model_producti 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_po_all_users,tukar.guling.po.all.users,model_tukar_guling_po,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 index 1556adcb..e008d2a1 100644 --- a/indoteknik_custom/views/tukar_guling.xml +++ b/indoteknik_custom/views/tukar_guling.xml @@ -1,127 +1,115 @@ <?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 + <data> + <!-- Action --> + <record id="action_pengajuan_tukar_guling" model="ir.actions.act_window"> + <field name="name">Pengajuan Tukar Guling SO</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" + name="Pengajuan Tukar Guling SO" + parent="sale.sale_order_menu" 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')" + <!-- Sequence --> + <record id="seq_tukar_guling" model="ir.sequence"> + <field name="name">Pengajuan Tukar Guling SO</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="ba_num" string="Nomor BA"/> + <field name="return_type" string="Return Type"/> + <field name="state" widget="badge" + decoration-info="state in ('draft', 'approval_sales', 'approval_logistic','approval_finance')" 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" + </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" + <button name="action_approve" string="Approve" type="object" + class="btn-primary" + attrs="{'invisible': [('state', 'not in', ['approval_sales', 'approval_logistic', 'approval_finance'])]}"/> + <button name="action_cancel" string="Cancel" type="object" class="btn-secondary" - attrs="{'invisible': [('state', 'in', ('done', 'cancel'))]}" + attrs="{'invisible': [('state', '=', 'draft')]}" confirm="Are you sure you want to cancel this record?"/> - <button name="action_draft" string="Set to Draft" type="object" + <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="return_type"/> - <field name="in_num" string="BU/In" - attrs="{ - 'invisible': [('return_type', 'not in', ['debit_memo', 'revisi_po', 'tukar_guling'])], - 'required': [('return_type', 'in', ['debit_memo', 'revisi_po'])] - }"/> - - <field name="out_num" string="BU/Out" + <field name="state" widget="statusbar" readonly="1" + statusbar_visible="draft,approval_sales,approval_logistic,approval_finance,done"/> + </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="return_type"/> + <field name="out_num" string="BU/Out" attrs="{ 'invisible': [('return_type', 'not in', ['revisi_so', 'credit_memo', 'tukar_guling'])], 'required': [('return_type', 'in', ['revisi_so', 'credit_memo'])] }"/> - </group> - <group> - <field name="ba_num" string="Nomor BA"/> - <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" + </group> + <group> + <field name="ba_num" string="Nomor BA"/> + <field name="notes"/> + </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" + <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> + </tree> + </field> + </page> + </notebook> + </sheet> + </form> + </field> + </record> + </data> </odoo>
\ No newline at end of file diff --git a/indoteknik_custom/views/tukar_guling_po.xml b/indoteknik_custom/views/tukar_guling_po.xml new file mode 100644 index 00000000..840b625b --- /dev/null +++ b/indoteknik_custom/views/tukar_guling_po.xml @@ -0,0 +1,117 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<odoo> + <data> + <!-- Action --> + <record id="action_pengajuan_tukar_guling_po" model="ir.actions.act_window"> + <field name="name">Pengajuan Tukar Guling PO</field> + <field name="type">ir.actions.act_window</field> + <field name="res_model">tukar.guling.po</field> + <field name="view_mode">tree,form</field> + </record> + + <menuitem + id="menu_pengajuan_tukar_guling_po" + name="Pengajuan Tukar Guling PO" + parent="purchase.menu_procurement_management" + sequence="4" + action="action_pengajuan_tukar_guling_po" + /> + + <!-- Sequence --> + <record id="seq_tukar_guling_po" model="ir.sequence"> + <field name="name">Pengajuan Tukar Guling PO</field> + <field name="code">tukar.guling.po</field> + <field name="prefix">PTGPO/</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_po_tree" model="ir.ui.view"> + <field name="name">pengajuan.tukar.guling.po.tree</field> + <field name="model">tukar.guling.po</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="ba_num" string="Nomor BA"/> + <field name="return_type" string="Return Type"/> + <field name="state" widget="badge" + decoration-info="state in ('draft', 'approval_sales', 'approval_logistic','approval_finance')" + decoration-success="state == 'done'" + decoration-muted="state == 'cancel'" + /> + </tree> + </field> + </record> + + <!-- Form View --> + <record id="pengajuan_tukar_guling_po_form" model="ir.ui.view"> + <field name="name">pengajuan.tukar.guling.po.form</field> + <field name="model">tukar.guling.po</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', 'not in', ['approval_sales', 'approval_logistic', 'approval_finance'])]}"/> + <button name="action_cancel" string="Cancel" type="object" + class="btn-secondary" + attrs="{'invisible': [('state', '=', 'draft')]}" + 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" + statusbar_visible="draft,approval_sales,approval_logistic,approval_finance,done"/> + </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="return_type"/> + <field name="out_num" string="BU/Out" + attrs="{ + 'invisible': [('return_type', 'not in', ['revisi_po', 'debit_memo', 'tukar_guling'])], + 'required': [('return_type', 'in', ['revisi_po', 'debit_memo'])] + }"/> + </group> + <group> + <field name="ba_num" string="Nomor BA"/> + <field name="notes"/> + </group> + </group> + <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> |
