diff options
| author | Mqdd <ahmadmiqdad27@gmail.com> | 2026-02-24 11:30:35 +0700 |
|---|---|---|
| committer | Mqdd <ahmadmiqdad27@gmail.com> | 2026-02-24 11:30:35 +0700 |
| commit | 2d47b479fd70a4731aaec0dfb4884cbd66f59de5 (patch) | |
| tree | 34c1e847a600ae530515735023eac25e3f2bb11d | |
| parent | 0b64d465d109392cdb4e634b1ccfffa56935d5e5 (diff) | |
| parent | 3b0686c3cd83dde114359e5a441d2d7b2c0ebc3f (diff) | |
Merge branch 'gudang-service' of https://bitbucket.org/altafixco/indoteknik-addons into odoo-backup
| -rwxr-xr-x | indoteknik_custom/__manifest__.py | 1 | ||||
| -rwxr-xr-x | indoteknik_custom/models/__init__.py | 2 | ||||
| -rw-r--r-- | indoteknik_custom/models/gudang_service.py | 273 | ||||
| -rw-r--r-- | indoteknik_custom/models/tukar_guling.py | 61 | ||||
| -rwxr-xr-x | indoteknik_custom/security/ir.model.access.csv | 3 | ||||
| -rw-r--r-- | indoteknik_custom/views/gudang_service.xml | 110 | ||||
| -rw-r--r-- | indoteknik_custom/views/ir_sequence.xml | 9 |
7 files changed, 459 insertions, 0 deletions
diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py index 3e39942b..8f51b9fc 100755 --- a/indoteknik_custom/__manifest__.py +++ b/indoteknik_custom/__manifest__.py @@ -191,6 +191,7 @@ 'views/uom_uom.xml', 'views/update_depreciation_move_wizard_view.xml', 'views/commission_internal.xml', + 'views/gudang_service.xml' 'views/keywords.xml', 'views/kartu_stock.xml', ], diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py index 840796f8..1fa640c4 100755 --- a/indoteknik_custom/models/__init__.py +++ b/indoteknik_custom/models/__init__.py @@ -165,6 +165,8 @@ from . import partial_delivery from . import domain_apo from . import uom_uom from . import commission_internal +from . import gudang_service from . import update_depreciation_move_wizard + from . import keywords from . import kartu_stock diff --git a/indoteknik_custom/models/gudang_service.py b/indoteknik_custom/models/gudang_service.py new file mode 100644 index 00000000..d699ccf4 --- /dev/null +++ b/indoteknik_custom/models/gudang_service.py @@ -0,0 +1,273 @@ +from odoo import models, fields, api, _ +from odoo.exceptions import UserError, ValidationError +import logging +from datetime import datetime +from collections import defaultdict + + +class GudangService(models.Model): + _name = "gudang.service" + _description = "Gudang Service" + _inherit = ["mail.thread", "mail.activity.mixin"] + _order = "id asc" + + name = fields.Char("Name", readonly=True) + partner_id = fields.Many2one("res.partner", string="Customer", readonly=True) + vendor_id = fields.Many2one("res.partner", string="Vendor Service", required=True) + origin = fields.Many2one( + "sale.order", + string="Origin SO", + required=True, + domain=[("state", "in", ["done", "sale"])], + ) + schedule_date = fields.Date(string="Schedule Date", required=True, tracking=True) + start_date = fields.Datetime(string="Date Processed", copy=False, tracking=True) + create_date = fields.Datetime( + string="Create Date", copy=False, tracking=True, default=fields.Datetime.now() + ) + done_date = fields.Datetime(string="Date Done", copy=False, tracking=True) + gudang_service_lines = fields.One2many( + "gudang.service.line", "gudang_service_id", string="Gudang Service Lines" + ) + # unprocessed_date = fields.Char( + # string='Unprocessed Since', + # compute='_compute_unprocessed_date' + # ) + remaining_date = fields.Char( + compute="_compute_remaining_date", string="Date Status" + ) + state = fields.Selection( + [ + ("draft", "Backlog"), + ("received_from_cust", "Received From Customer"), + ("sent_to_vendor", "Sent to Service Vendor"), + ("received_from_vendor", "Received From Service Vendor"), + ("delivered_to_cust", "Delivered to Customer"), + ("cancel", "Cancel"), + ], + default="draft", + tracking=True, + ) + cancel_reason = fields.Text("Cancel Reason", tracking=True) + + def check_duplicate_docs(self): + for rec in self: + found = self.env["gudang.service"].search( + [ + ("id", "!=", self.id), + ("origin.id", "=", self.origin.id), + ("partner_id.id", "=", rec.partner_id.id), + ("vendor_id.id", "=", rec.vendor_id.id), + ("schedule_date", "=", rec.schedule_date), + ( + "gudang_service_lines.product_id.name", + "=", + rec.gudang_service_lines.product_id.name, + ), + ( + "gudang_service_lines.quantity", + "=", + rec.gudang_service_lines.quantity, + ), + ] + ) + if found: + raise UserError("This Document has duplicate with %s" % found.name) + + def _send_logistic_notification(self): + group = self.env.ref( + "indoteknik_custom.group_role_logistic", raise_if_not_found=False + ) + if not group: + return + + users = group.users + # MD + md = self.env["res.users"].browse([3425, 4801, 1036]) + # send to logistic and MD + users = users | md + + if not users: + return + + # Logistic users to be excluded + excluded_users = [7, 17098, 216, 28, 15710] + + for rec in self: + for user in users: + if user.id in excluded_users: + continue + self.env["mail.activity"].create( + { + "res_model_id": self.env["ir.model"]._get_id("gudang.service"), + "res_id": rec.id, + "activity_type_id": self.env.ref( + "mail.mail_activity_data_todo" + ).id, + "user_id": user.id, + "summary": "Gudang Service On Progress", + "note": _( + "Ada Jadwal Service Barang di Document <b>%s</b> Jadwal Service 📅 <b>%s</b>" + ) + % (rec.name, rec.schedule_date), + # 'date_deadline': fields.Date.today(), + } + ) + + # kirim ke private message odoo + channel = ( + self.env["mail.channel"] + .channel_get([self.env.user.partner_id.id, user.partner_id.id]) + .get("id") + ) + if not channel: + continue + res = self.env["mail.channel"].browse(channel) + res.with_user(self.env.user.browse(25)).message_post( + body=_( + "Ada Jadwal Service Barang di Document <b>%s</b> Jadwal Service 📅 <b>%s</b>" + ) + % (rec.name, rec.schedule_date), + message_type="comment", + subtype_xmlid="mail.mt_comment", + ) + + @api.model + def cron_notify_onprogress_gudang_service(self): + records = self.search( + [ + ("state", "in", ["draft", "received_from_customer"]), + ] + ) + + if records: + records._send_logistic_notification() + + @api.depends("schedule_date", "create_date") + def _compute_remaining_date(self): + today = fields.Date.today() + + for rec in self: + if not rec.schedule_date: + rec.remaining_date = "-" + continue + + base_date = rec.create_date.date() if rec.create_date else today + + schedule = rec.schedule_date + days = (schedule - base_date).days + + if days > 0: + rec.remaining_date = _("In %s days") % days + elif days == 0: + rec.remaining_date = _("Today") + else: + rec.remaining_date = _("Overdue %s days") % abs(days) + + def action_submit(self): + self.ensure_one() + self.check_duplicate_docs() + for rec in self: + if rec.state == "draft": + rec.state = "received_from_cust" + elif rec.state == "received_from_cust": + rec.state = "sent_to_vendor" + rec.start_date = fields.Datetime.now() + elif rec.state == "sent_to_vendor": + rec.state = "received_from_vendor" + + def action_done(self): + for rec in self: + if rec.state != "received_from_vendor": + raise UserError("Only 'Received From Vendor' state can be set to Done") + + rec.activity_ids.unlink() + + rec.write( + {"state": "delivered_to_cust", "done_date": fields.Datetime.now()} + ) + + def action_draft(self): + """Reset to draft state""" + for rec in self: + rec.cancel_reason = False + if rec.state == "cancel": + rec.write({"state": "draft"}) + else: + raise UserError("Only Canceled Record Can Be Reset To Draft") + + def action_cancel(self): + for rec in self: + activities = self.env["mail.activity"].search( + [ + ("res_id", "=", rec.id), + ("res_model", "=", "gudang.service"), + ] + ) + activities.unlink() + if rec.state == "delivered_to_cust": + raise UserError("You cannot cancel a done record") + if not rec.cancel_reason: + raise UserError("Cancel Reason must be filled") + rec.start_date = False + rec.done_date = False + rec.state = "cancel" + + @api.model + def create(self, vals): + # sequence + if not vals.get("name") or vals["name"] == "New": + vals["name"] = self.env["ir.sequence"].next_by_code("gudang.service") + + # partner dari SO + so = self.env["sale.order"].browse(vals["origin"]) + vals["partner_id"] = so.partner_id.id + + res = super(GudangService, self).create(vals) + + res.check_duplicate_docs() + res._send_logistic_notification() + return res + + def write(self, vals): + if vals.get("origin"): + so = self.env["sale.order"].browse(vals["origin"]) + vals["partner_id"] = so.partner_id.id + vals.check_duplicate_docs() + + return super(GudangService, self).write(vals) + + @api.onchange("origin") + def _onchange_origin(self): + if not self.origin: + self.gudang_service_lines = [(5, 0, 0)] + return + + self.partner_id = self.origin.partner_id + + lines = [] + for line in self.origin.order_line: + lines.append( + ( + 0, + 0, + { + "product_id": line.product_id.id, + "quantity": line.product_uom_qty, + "origin_so": self.origin.id, + }, + ) + ) + + # hapus line lama lalu isi baru + self.gudang_service_lines = [(5, 0, 0)] + lines + + +class GudangServiceLine(models.Model): + _name = "gudang.service.line" + _inherit = ["mail.thread", "mail.activity.mixin"] + + product_id = fields.Many2one("product.product", string="Product") + quantity = fields.Float(string="Quantity") + origin_so = fields.Many2one("sale.order", string="Origin SO") + gudang_service_id = fields.Many2one("gudang.service", string="Gudang Service ID") diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index 682c478a..619e7c99 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -62,6 +62,7 @@ class TukarGuling(models.Model): notes = fields.Text('Notes') return_type = fields.Selection(String='Return Type', selection=[ ('tukar_guling', 'Tukar Guling'), # -> barang yang sama + # ('service', 'Service'), # -> barang yang sama ('retur_so', 'Retur SO')], required=True, tracking=3, help='Retur SO (ORT-SRT),\n Tukar Guling (ORT-SRT-PICK-OUT)') state = fields.Selection(string='Status', selection=[ ('draft', 'Draft'), @@ -931,6 +932,66 @@ class TukarGuling(models.Model): _logger.info(f"✅ BU/PICK Baru dari ORT created: {new_pick.name}") record.message_post( body=f"📦 <b>{new_pick.name}</b> created by <b>{self.env.user.name}</b> (state: <b>{new_pick.state}</b>)") + + # if record.return_type == 'service': + # GUDANG_SERVICE_LOCATION_ID = 98 + # # From STOCK to OUTPUT + # done_service = self.env['stock.picking'].create({ + # 'group_id': bu_out.group_id.id, + # 'tukar_guling_id': record.id, + # 'sale_order': record.origin, + # 'note': record.notes, + # 'picking_type_id': 32, + # 'location_id': GUDANG_SERVICE_LOCATION_ID, + # 'location_dest_id': BU_STOCK_LOCATION_ID, + # 'partner_id': bu_out.partner_id.id, + # 'move_ids_without_package': [(0, 0, { + # 'product_id': line.product_id.id, + # 'product_uom_qty': line.product_uom_qty, + # 'product_uom': line.product_uom.id, + # 'name': line.product_id.display_name, + # 'location_id': GUDANG_SERVICE_LOCATION_ID, + # 'location_dest_id': BU_STOCK_LOCATION_ID, + # }) for line in record.line_ids], + # }) + # if done_service: + # done_service.action_confirm() + # done_service.action_assign() + # else: + # raise UserError("Gagal membuat picking service") + + # service_to_output = self.env['stock.picking'].create({ + # 'group_id': bu_out.group_id.id, + # 'tukar_guling_id': record.id, + # 'sale_order': record.origin, + # 'note': record.notes, + # 'picking_type_id': 32, + # 'location_id': BU_STOCK_LOCATION_ID, + # 'location_dest_id': BU_OUTPUT_LOCATION_ID, + # 'partner_id': bu_out.partner_id.id, + # 'move_lines': [(0, 0, { + # 'product_id': line.product_id.id, + # 'product_uom_qty': line.product_uom_qty, + # 'product_uom': line.product_uom.id, + # 'name': line.product_id.display_name, + # 'location_id':BU_STOCK_LOCATION_ID, + # 'location_dest_id': BU_STOCK_LOCATION_ID, + # }) for line in record.line_ids], + # 'move_ids_without_package': [(0, 0, { + # 'product_id': line.product_id.id, + # 'product_uom_qty': line.product_uom_qty, + # 'product_uom': line.product_uom.id, + # 'name': line.product_id.display_name, + # 'location_id': BU_STOCK_LOCATION_ID, + # 'location_dest_id': BU_OUTPUT_LOCATION_ID, + # }) for line in record.line_ids], + # }) + # if service_to_output: + # service_to_output.action_confirm() + # service_to_output.action_assign() + # else: + # raise UserError("Gagal membuat picking service") + # BU/OUT Baru dari SRT if srt_picking: diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv index b6583ed5..87798115 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -216,5 +216,8 @@ access_surat_piutang_user,surat.piutang user,model_surat_piutang,,1,1,1,1 access_surat_piutang_line_user,surat.piutang.line user,model_surat_piutang_line,,1,1,1,1 access_sj_tele,access.sj.tele,model_sj_tele,base.group_system,1,1,1,1 access_stock_picking_sj_document,stock.picking.sj.document,model_stock_picking_sj_document,base.group_user,1,1,1,1 +access_gudang_service,gudang.service,model_gudang_service,base.group_user,1,1,1,1 +access_gudang_service_line,gudang.service.line,model_gudang_service_line,base.group_user,1,1,1,1 access_update_depreciation_move_wizard,access.update.depreciation.move.wizard,model_update_depreciation_move_wizard,,1,1,1,1 + access_keywords,keywords,model_keywords,base.group_user,1,1,1,1 diff --git a/indoteknik_custom/views/gudang_service.xml b/indoteknik_custom/views/gudang_service.xml new file mode 100644 index 00000000..769664c5 --- /dev/null +++ b/indoteknik_custom/views/gudang_service.xml @@ -0,0 +1,110 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<odoo> + <data> + <!-- Tree --> + <record id="view_gudang_service_tree" model="ir.ui.view"> + <field name="name">gudang.serivice.tree</field> + <field name="model">gudang.service</field> + <field name="arch" type="xml"> + <tree string="Monitoring Barang Service" decoration-danger="state in ('draft', 'received_from_cust')" decoration-warning="state in ('sent_to_vendor', 'received_from_vendor')" + decoration-success="state == 'delivered_to_cust'" decoration-muted="state == 'cancel'" > + <field name="name"/> + <field name="partner_id"/> + <field name="vendor_id"/> + <field name="origin"/> + <field name="schedule_date"/> + <field name="start_date" optional="hide"/> + <field name="remaining_date"/> + <field name="state" widget="badge" decoration-danger="state in ('draft', 'received_from_cust')" decoration-warning="state in ('sent_to_vendor', 'received_from_vendor')" + decoration-success="state == 'delivered_to_cust'" decoration-muted="state == 'cancel'" /> + <field name="cancel_reason" optional="hide"/> + <field name="create_date" optional="hide"/> + </tree> + </field> + </record> + <!-- Form --> + <record id="view_gudang_service_form" model="ir.ui.view"> + <field name="name">gudang.service.form</field> + <field name="model">gudang.service</field> + <field name="arch" type="xml"> + <form> + <header> + <button name="action_submit" string="Proceed" type="object" + class="btn-primary" + attrs="{'invisible': [('state', 'in', ['cancel', 'done', 'received_from_vendor', 'delivered_to_cust'])]}"/> + <button name="action_done" string="Set Done" type="object" + class="btn-primary" + attrs="{'invisible': [('state', 'not in', ['received_from_vendor'])]}"/> + <button name="action_cancel" string="Cancel" type="object" + class="btn-secondary" + attrs="{'invisible': [('state', 'in', ['cancel', 'delivered_to_cust'])]}"/> + <button name="action_draft" string="Set to Backlog" type="object" + class="btn-secondary" + attrs="{'invisible': [('state', 'not in', ['cancel'])]}"/> + <field name="state" widget="statusbar" readonly="1"/> + </header> + <sheet> + <div class="oe_title"> + <h1> + <field name="name" readonly="1" class="oe_inline"/> + </h1> + </div> + <group> + <field name="origin" attrs="{'readonly': [('state', 'not in', ['draft'])]}"/> + <field name="partner_id"/> + <field name="vendor_id"/> + <field name="remaining_date"/> + <field name="schedule_date" attrs="{'readonly': [('state', 'not in', ['draft', 'reveived_from_cust'])]}"/> + <field name="start_date" readonly="1"/> + <field name="done_date" attrs="{'invisible': [('state', 'not in', ['delivered_to_cust'])]}"/> + <field name="create_uid"/> + <field name="cancel_reason" + attrs="{'invisible': [('state', 'in', ['delivered_to_cust', 'draft'])]}"/> + </group> + <notebook> + <page string="Product Lines" name="product_lines"> + <field name="gudang_service_lines"> + <tree string="Product Lines" editable="top" create="0" delete="1"> + <field name="product_id"/> + <field name="quantity"/> + </tree> + </field> + </page> + </notebook> + </sheet> + <div class="oe_chatter"> + <field name="message_follower_ids" widget="mail_followers"/> + <field name="message_ids" widget="mail_thread"/> + </div> + </form> + </field> + </record> + <!-- Action --> + <record id="action_gudang_service" model="ir.actions.act_window"> + <field name="name">Monitoring Barang Service</field> + <field name="type">ir.actions.act_window</field> + <field name="res_model">gudang.service</field> + <field name="view_mode">tree,form</field> + </record> + + <!-- Menu --> + <menuitem + id="menu_gudang_service" + name="Monitoring Barang Service" + parent="indoteknik_custom.menu_monitoring_in_sale" + sequence="10" + action="action_gudang_service" + /> + </data> + <!-- Cron --> + <record id="ir_cron_gudang_service_logistik_notify" model="ir.cron"> + <field name="name">Gudang Service Daily Notification</field> + <field name="model_id" ref="model_gudang_service"/> + <field name="state">code</field> + <field name="code">model.cron_notify_onprogress_gudang_service()</field> + <field name="interval_number">1</field> + <field name="interval_type">days</field> + <field name="numbercall">-1</field> + <field name="active">False</field> + </record> +</odoo> diff --git a/indoteknik_custom/views/ir_sequence.xml b/indoteknik_custom/views/ir_sequence.xml index 46148606..55e48300 100644 --- a/indoteknik_custom/views/ir_sequence.xml +++ b/indoteknik_custom/views/ir_sequence.xml @@ -260,5 +260,14 @@ <field name="number_next">1</field> <field name="number_increment">1</field> </record> + + <record id="seq_gudang_service" model="ir.sequence"> + <field name="name">Gudang Service</field> + <field name="code">gudang.service</field> + <field name="prefix">MGS/%(year)s/%(month)s/</field> + <field name="padding">4</field> + <field name="number_next">1</field> + <field name="number_increment">1</field> + </record> </data> </odoo>
\ No newline at end of file |
