Serba-serbi Error, Penyebab dan Solusinya

Bagi anda yang sudah terbiasa develop odoo pasti tidak asing lagi dengan penampakan di atas, hampir setiap error di odoo akan menampilkan keterangan error seperti itu. Jadi bagi anda yang baru memulai develop dan menemukan error seperti itu jangan panik. Di bawah ini saya coba list beberapa error yang sering dijumpai. Meskipun tampilan errornya sama tapi sebenarnya penyebabnya berbeda-beda. Untuk mengetahui penyebabnya maka kita harus cek log nya. Dan berikut ini saya kumpulkan beberapa jenis error yang sering ditemui beserta solusinya :


Bagaimana Cara Mengatasi Error ssh: sign_and_send_pubkey: signing failed: agent refused operation ?

Ini terjadi ketika saya baru saja menginstal ulang ubuntu 16.04 dan mau mengkonfigurasi project agar terhubung ke gitlab.

Kondisi :

  • Sudah generate ssh-keygen menggunakan user ubuntu biasa (bukan root) dan menambahkan ssh-key tersebut ke user gitlab
  • Mencoba git clone salah satu project menggunakan URL ssh (git@gitlab.com:mif_salam/nama_project.git) dan mendapatkan error seperti di atas
  • Kemudian mencoba git pull di salah satu project yang sudah terhubung ke gitlab dan terjadi error yang sama

Solusi :

  • Setelah coba googling banyak yang menyarankan untuk menjalankan sintak ssh-add dengan tujuan menambah user identity pada file id_rsa (privat key)
  • Dilihat dari komentarnya banyak yang berhasil setelah menjalankan sintak tersebut, namun bagi saya tidak
  • Dan akhirnya saya menemukan solusi dengan cara menguninstal gnome-keyring kemudian reboot/log out ubuntunyasudo apt-get remove gnome-keyring
  • Sekarang sudah bisa menjalankan perintah git seperti biasa

Print QWeb Tidak Ada Garis dan Berantakan

Apakah anda pernah mengalami hasil print seperti gambar di atas? Loading saat download lama dan hasil print nya pun berantakan.

Jadi ceritanya saya dan tim (tim developer dan tim infrastruktur) menggunakan private server yang hanya bisa diakses menggunakan VPN untuk keperluan implementasi. Server disediakan oleh tim infra dan odoo diinstal oleh tim dev. Singkat cerita odoo sudah berhasil diinstal dan berjalan lancar termasuk print qweb dapat diprint seperti biasanya. Sampai kemudian proses develop selesai dan akan memasuki fase training dan UAT, yang artinya user akan mencoba langsung sistem yang sudah kita develop.

Read More


Catatan Singkat Teknikal (Beberapa Syntax Yang Sering Dipakai)

1. File .py

override method create

class ms_res_partner(models.Model):
    _inherit = 'res.partner'
    
    ...
 
    @api.model
    def create(self, vals):
        vals['comment'] = "ini adalah notes"
        return super(ms_res_partner, self).create(vals)

override method write

class ms_res_partner(models.Model):
    _inherit = 'res.partner'
    
    ...
 
    @api.multi
    def write(self, vals):
        vals['comment'] = "ini adalah notes"
        return super(ms_res_partner, self).write(vals)

menggunakan digit precision

import odoo.addons.decimal_precision as dp

...

amount_total = fields.Float(string='Total Harga', compute='_get_tot_line', readonly=True, digits=dp.get_precision('Product Price'))

override method unlink

@api.multi
def unlink(self):
    for me_id in self :
        if me_id.state != 'draft' :
            raise Warning("Tidak bisa delete transaksi !")
    return super(ms_bayar_resep, self).unlink()

override method name_get

@api.multi
def name_get(self):
    result = []
    for me_id in self :
        result.append((me_id.id, "%s [%s]"%(me_id.name, me_id.partner_id.name)))
    return result

override method name_search

@api.model
def name_search(self, name, args=None, operator='ilike', limit=100):
    args = args or []
    if name :
        recs = self.search([
            '|',
            ('name', operator, name),
            ('partner_id.name', operator, name)
        ] + args, limit=limit)
    else :
        recs = self.search([] + args, limit=limit)
    return recs.name_get()

menambah value baru field Selection ketika inherit

field_name = fields.Selection(selection_add=[
    ('value1','Value 1'),
    ('value2','Value 2')
])

memanggil action report

@api.multi
def nota_penerimaan_barang(self):
    return self.env["report"].get_action(self, 'stock.report_deliveryslip')

override method default_get

@api.model
def default_get(self, fields):
    res = super(product_product, self).default_get(fields)
    if 'material' in self._context :
        res['type'] = 'product'
    return res

sql constraint

_sql_constraints = [
    ('unique_name', 'unique(name)', 'Nama duplicate !'),
]

return action

picking_ids = self.env['stock.picking'].search([])
action = self.env.ref('stock.action_picking_tree_all')
return {
    'name': action.name,
    'help': action.help,
    'type': action.type,
    'view_type': action.view_type,
    'view_mode': action.view_mode,
    'target': action.target,
    'res_model': action.res_model,
    'domain': [('id', 'in', picking_ids.ids)],
}

2. File .xml

menggunakan attrs

<field name="parent_id"/>
<field name="category_id" attrs="{'required': [('parent_id','=',False)]}"/>
<field name="code" attrs="{'required': [('parent_id','=',False)], 'readonly': [('parent_id','!=',False)]}"/>

set color pada tree

<tree string="Analytic Line" editable="top" colors="darkmagenta:has_child==True; blue:state=='draft'">
    <field name="state"/>
    <field name="has_child"/>
</tree>

Insya Allah syntax yang lain akan ditambahkan…

Terimakasih, semoga bermanfaat dan CMIIW…

Tutorial odoo/openerp Indonesia


Mengatur Hak Akses (Security)

Agar lebih mudah memahaminya, mari kita kelompokkan hak akses ini menjadi 3 bagian :

  1. Akses terhadap model/object (CRUD – Create, Read, Update, Delete)
    Misalnya mengatur apakah user mempunyai akses Read terhadap object ms.pendaftaran atau tidak? Jika ya maka dia bisa melihat data-data yang ada di object ms.pendaftaran
  2. Akses terhadap menu, fields, button
    User sudah mempunyai akses Read terhadap object ms.pendaftaran tapi dia tidak boleh mengetahui nama pasien yang mendaftar. Maka kita bisa menyembunyikan field pasien dari user bersangkutan. Begitupun dengan button dan menu kita bisa mengatur kepada user siapa saja button dan menu tersebut akan dimunculkan
  3. Akses terhadap spesifik data/record
    Meskipun user sudah mempunyai akses Read terhadap object ms.pendaftaran juga terhadap menu, fields dan button. Tapi dia hanya boleh melihat data pendaftaran yang poli tujuannya adalah poli jantung dan tidak boleh melihat poli lainnya. Maka hal tersebut bisa diatur di sini

Itulah kurang lebih pengelompokkan hak akses di odoo. Dalam penjelasan singkat di atas saya memberikan gambaran jika hak akses tersebut diterapkan pada object ms.pendaftaran di modul ms_puskesmas yang pernah saya berikan di Belajar Odoo Untuk Pemula [Part 8/8]. Jika anda tidak mengikutinya silahkan download di sini karena dalam tutorial ini saya akan menggunakan modul tersebut.

Untuk membuat setting hak akses ini kita bisa melakukannya melalui coding ataupun langsung melalui aplikasi. Dan sekarang kita akan membuatnya melalui aplikasi.

Pertama-tama silahkan instal modul yang sudah didownload, tapi sebelumnya anda harus comment dulu baris “security/ir.model.access.csv” di __manifest__.py karena kita akan membuat semua hak akses melalui aplikasi

Mari kita mulai dengan membuat user baru dengan nama Agus dengan setting seperti berikut

Kemudian set password dengan klik button Change Password. Isi password bebas

Buka tab incognito atau private browser dan coba login dengan user Agus

Ada 2 top menu (Discuss dan Puskesmas), satu grup menu (Data Master) dan 3 menu yang pertama kali muncul saat login menggunakan user Agus. Kenapa demikian?

  1. Ketiga menu tersebut beserta grup menu Data Master dan top menu Puskesmas merupakan menu baru yang kita buat di modul ms_base dan ms_puskesmas, yang semuanya belum di set/assign ke user dan group user manapun. Secara default jika kita tidak assign menu ke user/group manapun maka menu tersebut bisa diakses oleh semua user termasuk user Agus.
  2. User Agus sudah diset masuk ke group Employee (bawaan odoo) karena ketika create user pada bagian Application > Employees kita memilih Employee. Dan pada group Employee tersebut sudah diberikan akses ke menu Discuss sehingga muncul di user Agus.
  3. Selain akses ke menu Discuss di group Employee juga dapat akses ke beberapa object, diantaranya object res.partner yang kita pakai untuk form Dokter dan Pasien serta object product.template yang dipakai untuk form Obat
  4. Sehingga dapat disimpulkan bahwa jika user sudah mempunyai akses terhadap object dan menu yang berisi object tersebut maka menu yang bersangkutan akan muncul. Jadi jika user hanya memiliki akses terhadap salah satu maka menu tidak akan muncul. Misalnya user sudah mempunyai akses terhadap menu tapi tidak mempunyai akses terhadap object dalam menu tersebut maka menu tidak akan muncul. Ataupun sebaliknya, sudah dapat akses terhadap object tapi tidak terhadap menu maka menu tidak akan muncul.

Jadi jika ada pertanyaan kenapa menu Ruangan, Poli, Pendaftaran dan yang lainnya tidak muncul padahal menu tersebut belum di assign ke group dan user manapun? Ya jawabannya karena user Agus belum mempunyai akses ke object-object pada menu tersebut.

Berbeda dengan menu yang ketika pertama kali create dan belum di assign ke group manapun menu tersebut bisa diakses oleh semua user. Maka object ketika pertama kali dibuat dia tidak bisa diakses oleh user manapun (kecuali Administrator/superuser). Sehingga kalau anda perhatikan di terminal ada warning seperti ini

Memberitahu bahwa object-object tersebut tidak mempunyai access rules sehingga kita perlu menambahkannya. Dari beberapa object yang tidak mempunyai access rules di atas kita akan coba mempraktekkannya salah satu, yaitu ms.pendaftaran

Dengan menggunakan user Administrator dan Developer Mode nya aktif silahkan masuk ke menu Settings > Technical > Security > Access Controls List

Tambahkan record baru seperti ini

Menu tersebut fungsinya adalah untuk mengatur akses terhadap object. Dalam gambar di atas artinya kita akan menambahkan akses terhadap object ms.pendaftaran (Pendaftaran) dengan nama ms_pendaftaran read (nama bebas) dan akses yang diberikan hanya read. Kolom group dikosongkan sehingga setting akses tersebut berlaku untuk semua group/user. Jika group diisi maka hanya akan berlaku untuk group bersangkutan.

Kembali lagi ke user Agus dan silahkan reload browser. Maka akan bertambah satu menu baru yaitu Pendaftaran (tidak ada tombol create dan edit karena akses yang diberikan hanya read)

Untuk testing kita create beberapa record pada data master dan transaksi menggunakan user administrator. Silahkan ikuti video berikut

Sudah ada 3 record di menu Pendaftaran, selanjutnya buat user baru dengan nama Budi, settingan sama dengan user Agus.

Karena setting untuk kedua user tersebut sama maka menu-menu dan data yang muncul pun sama. Jika kita ingin membedakannya, misal Agus hanya bisa melihat data Pendaftaran yang poli tujuannya adalah Poli Gizi sementara Budi bisa melihat semua data Pendaftaran maka harus membuat group baru untuk kedua user tersebut. Menu nya terletak di Settings > Users > Groups

Buat group baru seperti di atas, isi hanya bagian Name dan tab Users. Dan buat satu lagi group Budi untuk user Budi

Sekarang kita sudah bisa membedakan akses bagi kedua user tersebut melalui setting group masing-masing. Tadi kita akan membuat Agus hanya bisa melihat data pendaftaran yang poli tujuannya adalah Poli Gizi sementara Budi bisa melihat semua data pendaftaran. Untuk melakukan itu silahkan masuk ke menu Settings > Technical > Security > Record Rules

Create record seperti berikut

Artinya kita membatasi data pada object ms.pendaftaran (Pendaftaran) untuk user yang masuk ke Group Agus hanya bisa mengakses data pendaftaran yang poli_id.kode=P001 (yaitu Poli Gizi).

User Budi tidak akan terkena pembatasan tersebut, karena tidak masuk ke Group Agus.

Pada bagian Group sebenarnya bisa dikosongkan, jika dikosongkan maka semua group akan terkena pembatasan tersebut (kecuali Administrator)

Jika ada pertanyaan, bagaimana jika satu object dan satu group memiliki lebih dari satu Record Rules? Maka itu boleh-boleh saja, pembatasan yang diterapkan merupakan kombinasi dari semuanya dengan menerapkan logic ‘|’ (atau). Misalnya ada dua pembatasan :

  1. Record Rule 1 isinya [(‘poli_id.kode’,’=’,’P001′)]
  2. Record Rule 2 [(‘poli_id.kode’,’=’,’P002′)]

Maka akan menampilkan data yang berisi kedua kode poli tersebut.

Namun jika ada case Record Rule yang dibuat otomatis oleh odoo namun kita tidak menginginkannya, maka kita bisa menonaktifkan Record Rules tersebut dengan men untick/uncheck field Active.

Kembali lagi ke testing, silahkan coba login lagi menggunakan user Agus dan Budi, masuk ke menu Pendaftaran maka akan terlihat perbedaannya.

Case selanjutnya kita akan set menu Obat hanya bisa diakses oleh Budi, maka caranya adalah edit group Budi dan pada tab Menu tambahkan menu Obat

Jika menu sudah ditambahkan di salah satu group maka menu itupun tidak lagi bisa diakses oleh semua group. Tapi hanya bisa diakses oleh group tertentu yang di dalamnya terdapat menu Obat tersebut. Dan menu Obat pun hilang dari user Agus. Tapi menu Obat ini tidak hanya bisa muncul di group Budi, jika ada group lain yang perlu memunculkan menu tersebut maka bisa ditambahkan juga.

Kalau sebelumnya Agus dan Budi sama-sama hanya mempunyai akses read terhadap data ms.pendaftaran, maka sekarang kita akan ubah Budi bisa melakukan proses CRUD. Caranya masih di group Budi edit dan tambahkan pada tab Access Right seperti berikut

Maka akan muncul tombol create dan edit di user Budi

Oke kita sudah berhasil mengatur akses terhadap object, menu dan spesifik data. Lalu bagaimana caranya untuk mengatur akses terhadap fields dan button? Berbeda dengan beberapa hal yang sudah kita lakukan sebelumnya yang bisa disetting melalui aplikasi, maka untuk mengatur akses fields dan button kita harus menambahkannya lewat coding. Yaitu dengan menambahkan attribute groups=”nama_addons.external_id_groups” dan group yang di assign di fields/button tersebut pun harus dibuat melalui coding karena harus membaca External ID

Sekarang tambahkan folder baru di addons ms_puskesmas yaitu ‘data’ dan file baru di dalamnya res_groups.xml. Isi dengan syntax berikut

<odoo>
    <data noupdate="0">
 
        <record id="group_visible" model="res.groups">
            <field name="name">Visible Button</field>
        </record>
 
    </data>
</odoo>

Kemudian edit button confirm, cancel dan field pasien_id di ms_puskesmas/views/ms_pendaftaran.xml dengan menambahkan attribute  groups=”ms_puskesmas.group_visible” seperti

<button name="action_confirm" string="Confirm" type="object" class="oe_highlight" attrs="{'invisible': [('state','!=','draft')]}" groups="ms_puskesmas.group_visible"/>

Jangan lupa tambahkan baris “data/res_groups.xml” di __manifest__.py, restart service dan upgrade.

Note : baris “data/res_groups.xml” harus terletak sebelum “views/ms_pendaftaran.xml” karena di “views/ms_pendaftaran.xml” membaca External ID di res_groups.xml. Jika tidak maka akan error karena External ID group dibaca sebelum record group tersebut masuk ke database.

Setelah upgrade modul maka button dan fields pasien_id hilang dari kedua user. Kemudian kita akan mengatur agar button Confirm, Cancel dan fields pasien_id hanya muncul di user Budi.

Agar ketiganya muncul kembali di user Budi maka ada dua cara :

  1. User Budi dimasukkan ke group Visible Button, atau
  2. Group Budi meng inherit Group Visible Button (edit group Budi dan pada tab Inherited tambahkan group Visible Button). Inherited ini artinya semua akses yang ada di group yang diinherit maka otomatis akan ditambahkan juga di group yang menginheritnya

Sehingga button dan field pasien_id akan muncul di user Budi

Saya rasa sudah cukup pembahasan mengenai hak akses ini. Tadi kita mempraktekannya dengan menambahkan langsung di aplikasi. Namun biasanya pembuatan hak akses ini lebih sering dilakukan melalui coding. Groups dan Record Rule ditambahkan melalui xml dan Access Control List melalui csv. Tapi kalau anda sudah memahami konsepnya maka tidak akan sulit untuk membuatnya lewat coding.

Note : Tutorial ini menggunakan Odoo 10

Terimakasih, semoga bermanfaat dan CMIIW…

Tutorial odoo/openerp Indonesia


Kamus Odoo

  • Field : akan menjadi kolom di database dan merupakan inputan di form
  • Object/model : contoh “ms.base” akan menjadi table ms_base di database
  • Funtional : memahami fungsi-fungsi dan flow odoo default
  • Technical : memahami coding dan cara membuat addons/modul
  • Addons/modul : modul python yang berisi codingan dan dapat diinstal sehingga memberikan efek terhadap aplikasi
  • Class : class python yang sama dengan class pada umumnya
  • Attribute : ada attribute class dan attribute field. Yaitu sesuatu yang dimiliki oleh class atau field dan berpengaruh terhadap class atau field itu sendiri
  • View : tampilan di odoo yang terdiri dari tree view, form view, search view, kanban view, dll
  • Wizard : form view yang bentuknya pop up
  • Workflow : alur proses, biasanya pada form transaksi
  • Inherit : konsep inheritance
  • Apps : menu di odoo yang berisi daftar addons/modul
  • Service odoo : untuk menjalankan odoo agar bisa diakses di browser
  • Developer mode : ada di menu settings, jika diaktifkan maka akan menampilkan beberapa menu yang di hide dan dapat menampilkan attribute field jika kursor disorotkan pada field bersangkutan

Terimakasih, semoga bermanfaat dan CMIIW…

Tutorial odoo/openerp Indonesia


Belajar Odoo Untuk Pemula [Part 8/8]

Inheritance

Model Inheritance

Odoo menyediakan dua mekanisme inheritance untuk memperluas model yang ada dengan cara yang modular.

Pertama, memungkinkan modul untuk memodifikasi behavior model yang didefinisikan pada modul lain, seperti :

  • Menambahkan field
  • Menambahkan constraint
  • Menambahkan method
  • Override field yang ada di model lain (mengubah attribute)
  • Override method di model lain
  • dll

Kedua, pendelegasian yang memungkinkan untuk menghubungkan semua record model terhadap parent model. Jenis kedua ini ditulis dengan syntax _inherits seperti yang sudah dibahas di part sebelumnya.

View Inheritance

View inheritance memungkinkan kita untuk menambah, memodifikasi ataupun menghapus tampilan view yang didefinisikan di parent nya.

View inheritance me refer ke parent menggunakan inherit_id. Setelah itu kita bisa memodifikasi isi dari view parent dengan menggunakan position.

<record id="ms_ir_module_module_search_view" model="ir.ui.view">
    <field name="name">ms.ir.module.module.search</field>
    <field name="model">ir.module.module</field>
    <field name="inherit_id" ref="base.view_module_filter"/>
    <field name="arch" type="xml">
 
        <field name="name" position="after">
            <field name="author"/>
            <field name="website"/>
            <filter name="filter_miftah" string="New Addons" domain="[('author','=','Miftahussalam')]"/>
        </field>
 
    </field>
</record>

position bisa berisi beberapa value :

  • inside : akan menempatkan field/button baru di akhir elemen yang cocok
  • replace : akan mereplace/meremove field/button dari model parent
  • before : akan menempatkan field/button baru sebelum elemen
  • after : akan menempatkan field/button baru setelah elemen
  • attributes : mengubah atribut dari elemen yang cocok dengan elemen atribut khusus yang ditambahkan

Praktek

Contoh inherit untuk menambahkan method salah satunya ada pada file ms_base/models/ir_sequence.py

Method tersebut ditambahkan untuk memudahkan pembuatan nomor sequence yang dipanggil di semua form transaksi (contoh: DFT/18/00005).

Pada file ms_puskesmas/models/res_partner.py inherit ke model res.partner dengan tujuan untuk menambahkan fields, constraint dan override method.

Selain menambahkan field kita juga bisa mengubah attribute fields dengan cara menulis ulang field beserta type nya yang sudah ada di model parent, dan kita hanya perlu menuliskan attribute yang mau diubah atau ditambahkan. Misal kita akan menggunakan field city di res.partner akan tetapi string nya mau diubah menjadi ‘Kota’, seperti ini :

Pertama-tama kita tambahkan field city di xml pada file ms_puskesmas/views/res_partner.xml di bagian record id “ms_pasien_form_view”

Terlihat pada tampilan aplikasi stringnya adalah ‘City’, kemudian kita akan ubah menjadi ‘Kota’. Untuk melakukan itu sebenarnya ada dua cara. Pertama ubah di xml dengan menggunakan attributes seperti yang diterapkan untuk field street di atas. Kedua ubah di py seperti yang akan kita praktekkan sekarang.

Masih di file res_partner.py tambahkan field :

city = fields.Char(string='Kota')

Silahkan restart service dan upgrade, maka string filed city akan berubah menjadi ‘Kota’

Pada ms_base/views/ir_module_module.xml merupakan contoh inherit view view_module_filter (tempat install addons pada menu Apps) untuk menambahkan field author, website dan filter, serta membuat default filter agar ketika klik menu Apps maka sudah otomatis ter filter hanya addons custom yang muncul.

Selanjutnya kita akan coba praktekkan mekanisme inheritance yang kedua, yaitu pendelegasian dengan menggunakan syntax _inherits.

Kita akan buat model master baru, yaitu model ms.room

Silahkan buat file baru di ms_puskesmas/models dengan nama ms_room.py dan isi dengan syntax berikut

from odoo import fields, api, models

class ms_room(models.Model):
    _name = "ms.room"
    _description = "Poli"
 
    name = fields.Char('Nama', required=True)
    kode = fields.Char('Kode', required=True, copy=False)
    posisi = fields.Char('Posisi Ruangan')

    _sql_constraints = [
        ('unique_kode', 'unique(kode)', 'Kode Poli duplicate, mohon cek kembali !'),
    ]
 
    @api.multi
    def name_get(self):
        result = []
        for me in self :
            if me.posisi :
                result.append((me.id, "%s - %s" % (me.posisi, me.name)))
            else :
                result.append((me.id, me.name))
        return result
 
    @api.model
    def name_search(self, name, args=None, operator='ilike', limit=100):
        args = args or []
        if name :
            recs = self.search([
                '|',
                ('posisi', operator, name),
                ('name', operator, name),
            ] + args, limit=limit)
        else :
            recs = self.search([] + args, limit=limit)
        return recs.name_get()

dan di ms_puskesmas/views dengan nama ms_room.xml

<odoo>
    <data>
 
        <record model="ir.ui.view" id="ms_room_tree_view">
            <field name="name">ms.room.tree</field>
            <field name="model">ms.room</field>
            <field name="arch" type="xml">
                <tree string="Ruangan" editable="top">
                    <field name="kode"/>
                    <field name="name"/>
                    <field name="posisi"/>
                </tree>
            </field>
        </record>
 
        <record id="view_ms_room_search" model="ir.ui.view">
            <field name="name">ms.room.search</field>
            <field name="model">ms.room</field>
            <field name="arch" type="xml">
                <search string="Search Ruangan">
                    <field name="name"/>
                    <field name="kode"/>
                    <field name="posisi"/>
                </search>
            </field>
        </record>
 
        <record model="ir.actions.act_window" id="ms_room_action">
            <field name="name">Ruangan</field>
            <field name="res_model">ms.room</field>
            <field name="view_type">form</field>
            <field name="view_mode">tree,form</field>
            <field name="context">{}</field>
            <field name="domain">[]</field>
        </record>
 
        <record id="ms_room_action_tree" model="ir.actions.act_window.view">
            <field eval="1" name="sequence"/>
            <field name="view_mode">tree</field>
            <field name="view_id" ref="ms_room_tree_view"/>
            <field name="act_window_id" ref="ms_room_action"/>
        </record>
 
        <menuitem action="ms_room_action" id="ms_room_menu" parent="ms_base.ms_data_master_submenu" sequence="25"/>
 
    </data>
</odoo>

Jangan lupa tambah import ms_room di ms_puskesmas/models/__init__.py dan update __manifest__.py untuk membaca file ms_puskesmas.xml

Kemudian update isi file ms_poli.py dan ms_poli.xml seperti berikut :

ms_poli.py

from odoo import fields, api, models

class ms_poli(models.Model):
    _name = "ms.poli"
    _description = "Poli"
    _inherits = {'ms.room': 'room_id'}
 
    room_id = fields.Many2one('ms.room', string='Ruangan', ondelete='restrict', required=True, auto_join=True)
    name = fields.Char(related='room_id.name', inherited=True)
    kode = fields.Char(related='room_id.kode', inherited=True)
 
    @api.multi
    def name_get(self):
        result = []
        for me in self :
            result.append((me.id, "%s - %s" % (me.kode, me.name)))
        return result
 
    @api.model
    def name_search(self, name, args=None, operator='ilike', limit=100):
        args = args or []
        if name :
            recs = self.search([
                '|',
                ('kode', operator, name),
                ('name', operator, name),
            ] + args, limit=limit)
        else :
            recs = self.search([] + args, limit=limit)
        return recs.name_get()

ms_poli.xml

<odoo>
    <data>

        <record model="ir.ui.view" id="ms_poli_tree_view">
            <field name="name">ms.poli.tree</field>
            <field name="model">ms.poli</field>
            <field name="arch" type="xml">
                <tree string="Poli">
                    <field name="kode"/>
                    <field name="name"/>
                    <field name="room_id" readonly="1" attrs="{'invisible': [('id','=',False)]}"/>
                    <field name="id" invisible="1"/>
                </tree>
            </field>
        </record>

        <record model="ir.ui.view" id="ms_poli_form_view">
            <field name="name">ms.poli.form</field>
            <field name="model">ms.poli</field>
            <field name="arch" type="xml">
 
                <form string="Poli">
                    <sheet>
                        <group col="4">
                            <field name="kode"/>
                            <field name="name"/>
                            <field name="room_id" readonly="1" attrs="{'invisible': [('id','=',False)]}" required="0"/>
                            <field name="id" invisible="1"/>
                        </group>
                    </sheet>
                </form>
 
            </field>
        </record>

        <record id="view_ms_poli_search" model="ir.ui.view">
            <field name="name">ms.poli.search</field>
            <field name="model">ms.poli</field>
            <field name="arch" type="xml">
                <search string="Search Poli">
                    <field name="name"/>
                    <field name="kode"/>
                    <field name="room_id"/>
                </search>
            </field>
        </record>

        <record model="ir.actions.act_window" id="ms_poli_action">
            <field name="name">Poli</field>
            <field name="res_model">ms.poli</field>
            <field name="view_type">form</field>
            <field name="view_mode">tree,form</field>
            <field name="context">{}</field>
            <field name="domain">[]</field>
        </record>
 
        <record id="ms_poli_action_tree" model="ir.actions.act_window.view">
            <field eval="1" name="sequence"/>
            <field name="view_mode">tree</field>
            <field name="view_id" ref="ms_poli_tree_view"/>
            <field name="act_window_id" ref="ms_poli_action"/>
        </record>

        <record id="ms_poli_action_form" model="ir.actions.act_window.view">
            <field eval="1" name="sequence"/>
            <field name="view_mode">form</field>
            <field name="view_id" ref="ms_poli_form_view"/>
            <field name="act_window_id" ref="ms_poli_action"/>
        </record>

        <menuitem action="ms_poli_action" id="ms_poli_menu" parent="ms_base.ms_data_master_submenu" sequence="30"/>

    </data>
</odoo>

Silahkan upgrade modul sehingga muncul menu baru di Data Master yaitu menu Ruangan

Jika belum berhasil/error silahkan ambil source code berikut puskesmas branch lanjut

ms.poli menginherits ms.room sehingga setiap create Poli baru maka akan otomatis membuat Ruangan baru dengan nama dan kode yang sama. Tapi tidak berlaku sebaliknya, ketika membuat ruangan baru tidak akan otomatis membuat poli. Karena setiap poli pasti membutuhkan ruangan, tapi tidak setiap ruangan ditujukan untuk poli.

Silahkan lakukan testing dengan membuat poli baru

Note : Jika sebelumnya master poli sudah terdapat record maka akan ada error akses. Cara mengatasinya silahkan hapus semua record poli lewat pgAdmin.

Berikut contoh penginputannya

Alhamdulillah, kita sudah menyelesaikan materi dasar odoo teknikal. Saya rasa jika anda sudah memahami materi-materi yang disampaikan dari part 1-8 ini maka sudah cukup untuk menumbuhkan benih-benih cinta terhadap odoo 😀

Saran saya setelah ini anda harus mempelajari satu materi lagi yang belum dibahas di serial ini dan cukup penting ketika implementasi, yaitu mengenai hak akses. Dan insya Allah di lain waktu saya juga akan membahasnya di blog ini.

Saya menyadari tutorial ini masih banyak kekurangan, dan insya Allah saya akan terus mengupdate dan melengkapinya agar lebih mudah difahami. Kemudian jika yang saya sampaikan terdapat kekeliruan mohon beri tahu saya karena saya juga masih belajar.

Terimakasih, semoga bermanfaat dan CMIIW…

Tutorial odoo/openerp Indonesia


Belajar Odoo Untuk Pemula [Part 7/8]

Workflow

Hampir semua form transaksi di odoo mempunyai field state (seperti yang terlihat pada gambar di bawah), field state tersebut berfungsi untuk melakukan tracking perkembangan proses seiring dengan berjalannya waktu. Perkembangan proses itulah yang dinamakan workflow.

Perubahan workflow akan di trigger oleh proses atau action-action tertentu, dan biasanya action tersebut dijalankan oleh button.

Button

Cara penulisan button bisa lihat di addons puskesmas. Button ada dua type :

  • action : ketika diklik akan memanggil record ir.actions.act_window yang didefinisikan di xml
  • object : ketika diklik akan memanggil method di py yang namanya sama dengan nama button yang didefinisikan

Method

Method atau function di odoo hampir sama dengan function di bahasa pemrograman lain. Trigger untuk memanggil method tersebut berbeda-beda. Ada yang dijalankan oleh button, ada yang dipanggil di method lain, ada yang ditrigger oleh field, dll. Gambar di atas merupakan contoh method yang dijalankan oleh button, yang salah satu action di dalamnya adalah untuk menjalankan workflow.

Ada beberapa method default yang dimiliki oleh sebuah model. Method-method tersebut otomatis ditambahkan ketika menambahkan model, meskipun kita tidak menuliskannya. Method-method tersebut adalah :

  • create(vals) → record : Membuat record baru untuk model bersangkutan. Record yang dibuat valuenya diambil dari parameter vals dengan format dictionary {‘nama_field1′:’value1’, ‘nama_field2′:’value2’, …} misal {‘name’:’PO00001′}
    return : record, misal purchase.order(1,)
  • browse([ids]) → records : Mengambil recordset dari parameter list ids. Misal browse([1,2,3]). Harus memberikan parameter minimal satu id.
    return : recordset, misal purchase.order(1,2,3)
  • unlink() : Menghapus data current recordset
    return : True
  • write(vals) : Mengubah value recordset yang valuenya diambil dari parameter vals, sama seperti method create
    return : True
  • read([fields]) : Membaca value dari field yang diinginkan, bisa spesifik field ataupun membaca seluruh fields yang ada di model yang bersangkutan. Jika ingin mengambil beberapa field maka harus memberikan parameter nama field dalam bentuk list, misal read([‘name’,’qty’]). Namun apabila ingin mengambil semua field maka tidak perlu memberikan parameter, misal read()
    return : dictionary, misal {‘name’:’PO00001′, ‘qty’:5}
  • search(args, offset=0, limit=None, order=None, count=False) : Mencari recordset berdasarkan filter yang diberikan pada args. Penjelasan dari masing-masing parameter di atas adalah :
    – args = kriteria filter/domain yang diinginkan (karena bagian ini cukup panjang maka akan dijelaskan di bawah). Jika ingin mengambil semua record yang ada pada model bersangkutan, maka bagian args cukup ditulis list kosong seperti []
    – offset (format int, default None) = jumlah hasil yang akan diabaikan. Misal jika hasil filter didapatkan 5 recordset dan offset nya samadengan 2 maka recordset yang didapat adalah 3 recorset (5-2=3)
    – limit (format int, default all) = kebalikan dari offset. Akan menampilkan maksimal record sesuai value dari limit. Misal jika record hasil filter ada 7 dan limit=5 maka yang ditampilkan hanya 5 record. Tapi jika limit=5 tapi hasil dari filter didapat 3 record maka yang ditampilkan adalah 3
    – order (format str, default None) = untuk mengurutkan hasil pencarian berdasarkan field tertentu secara ascending atau descending. order ini bisa dipadukan dengan offset atau limit. Misal jika saya ingin mengambil satu record terakhir maka bisa tulis search([], limit=1, order=’id desc’)
    – count (format bool, default False) = jika True maka hanya akan menghitung jumlah record yang sesuai dengan kriteria filter dan return nilai jumlah record yang sesuai. Misal jika record yang dihasilkan adalah 3 record maka return nya adalah angka 5 (int)
    return : recordset, misal purchase.order(1,2,3). Atau return int, misal 3
  • search_count(args) → int : Menghitung jumlah record yang sesuai dengan kriteria pencarian. Fungsi ini hampir sama dengan search([], count=True)
    return : int
  • name_search(name=”, args=None, operator=’ilike’, limit=100) → records : Biasanya dipakai ketika ada relasi Many2one dari model lain ke model yang bersangkutan. Fungsinya untuk mencari record yang sesuai saat mengetikkan sesuatu pada field Many2one dengan kriteria filter yang sudah ditentukan pada args. Penjelasan dari masing-masing parameter :
    – name (type str) : inputan yang kita ketik pada field many2one
    – args (type list) : opsional search domain
    – operator (type str) : operator pembanding untuk domain pencarian seperti ‘like’ atau ‘=’
    – limit (type int) : maksimal record yang akan direturn (sama dengan limit pada method search())
    return : list pasangan antara id dan name, misal [(6, ‘DFT/18/00005’),(7, ‘DFT/18/00006’)]
  • fields_get([fields][, attributes]) : Mengambil daftar fields beserta attibutenya. Bisa spesifik field-field dan attribute tertentu atau mengambil semua fields dan attributes. Penjelasan parameter :
    – fields (type list) : diisi dengan nama field jika ingin spesifik mengambil field tertentu atau tidak perlu diisi jika ingin mengambil semuanya
    – attributes (type list) : diisi dengan nama attribute field jika ingin spesifik mengambil attribute tertentu atau tidak perlu diisi jika ingin mengambil semuanya
    return : dictionary {‘nama_field’ : {‘nama_attribute’:’value_attribute’}}
  • default_get(fields) → default_values : Memberikan default value fields. Parameter fields berisi list nama field yang ada pada model bersangkutan, misal [‘name’,’date’]
    return : dictionary, misal {‘name’:’ini default value’}
  • copy(default=None) : Menduplikasi record pada model tertentu yang value field dari record baru nya akan sama persis dengan record yang diduplikasi. Kecuali field-field yang attribute copy=False. Parameter default berisi dictionary {‘nama_field’:’value’}. Jika anda ingin mengisi value field record baru dengan value yang berbeda dari record lama, maka bisa menambahkan dictionary pada parameter default tersebut denga value yang diinginkan
    return : record, misal purchase.order(1,)
  • name_get() → [(id, name), …] : Biasanya dipakai untuk mengubah nama yang akan ditampilkan pada field Many2one. Secara default nama yang ditampilkan adalah mengambil dari field name atau _rec_name, tapi jika method ini di override maka nama yang tampil adalah sesuai dengan yang ditentukan di sini. Dan biasanya override name_get() selalu berbarengan dengan name_search(), karena pencarian pada field Many2one harus berdasarkan apa yang ditampilkan agar user tidak bingung
    return : list pasangan antara id dan display name, misal [(6, ‘DFT/18/00005’),(7, ‘DFT/18/00006’)]
  • dll

Selain method default kita juga bisa menambahkan method-method lain yang diperlukan sesuai dengan konsep OOP.

Ketika membuat method/function di python kita bisa menambahkan parameter ataupun tidak. Jika method mempunyai parameter maka saat memanggilnya pun kita harus memberikan value untuk parameter tersebut. Dan kalau anda perhatikan pada beberapa method yang mempunyai parameter, cara penulisannya ada yang hanya menuliskan parameter dan ada juga yang ditulis dengan value nya menggunakan samadengan. Itu artinya jika hanya menuliskan parameter maka parameter tersebut wajib disertakan ketika pemanggilan. Tapi jika menggunakan samadengan maka parameter tidak wajib disertakan dan jika tidak disertakan saat pemanggilan maka value dari parameter tersebut secara default adalah sesuai dengan yang ditulis di method itu.

Contoh : def get_product(self, default_code, type=’product’)
maka cara pemanggilannya bisa:
– self.get_product(‘A001′,’consu’), atau
– self.get_product(‘A001’)

Untuk cara pemanggilan pertama maka value dari type adalah ‘consu’. Sementara pada pemanggilan kedua value type adalah ‘product’

Domains

Domain adalah list kriteria pencarian record yang ditulis dengan format [(nama_field, operator, value)], dengan penjelasan masing-masing sebagai berikut :

  • nama_field (type str) : nama field pada model bersangkutan atau nama field pada model lain yang mempunyai relasi Many2one dengan model tersebut. Misal ‘alamat’ atau ‘partner_id.name’ (partner_id adalah field Many2one yang ada pada current model dan name adalah field yang ada pada model relasi)
  • operator (type str) : digunakan untuk mencocokkan antara nama_field dan value. Operator yang bisa digunakan adalah :

= sama dengan (case sensitive)
Misal : [(‘name’, ‘=’, ‘dog’)], maka akan mencari name ‘dog’ (case sensitive)

!= tidak sama dengan (case sensitive)
Misal : [(‘name’, ‘!=’, ‘dog’)], maka akan mencari name selain ‘dog’ (case sensitive)

> lebih dari
Misal : [(‘qty’, ‘>’, 2)], maka akan mencari qty 3,4,5,dst

>= lebih dari atau sama dengan
Misal : [(‘qty’, ‘>=’, 2)], maka akan mencari qty 2,3,4,5,dst

< kurang dari
Misal : [(‘qty’, ‘<‘, 2)], maka akan mencari qty 1,0,-1,-2,dst

<= kurang dari atau sama dengan
Misal : [(‘qty’, ‘<=’, 2)], maka akan mencari qty 2,1,0,-1,-2,dst

=? tidak diisi (None atau False) atau sama dengan (case sensitive)
Misal : [(‘name’, ‘=?’, ‘dog’)], maka akan mencari name ‘dog’ atau name ‘ ‘ / False / None

=like hampir sama dengan fungsi samadengan, tapi bisa menambahkan % pada inputan sehingga fungsinya jadi sama dengan like (case sensitive)
Misal : [(‘name’, ‘=like’, ‘dog’)], maka akan mencari name ‘dog’. Jika pada inputan ditulis %dog% maka akan mencari name ‘dog’, ‘doggy’, ‘bulldog’

like mengandung karakter (case sensitive)
Misal : [(‘name’, ‘like’, ‘dog’)], maka akan mencari name ‘dog’, ‘doggy’, ‘bulldog’

not like kebalikan dari like, tidak mengandung karakter (case sensitive)

ilike mengandung karakter (tidak case sensitive)
Misal : [(‘name’, ‘ilike’, ‘dog’)], maka akan mencari name ‘dog’, ‘doggy’, ‘bulldog’, ‘Dog’, ‘dOg’

not ilike kebalikan dari ilike, tidak mengandung karakter (tidak case sensitive)

=ilike hampir sama dengan fungsi samadengan, tapi bisa menambahkan % pada inputan sehingga fungsinya jadi sama dengan ilike (tidak case sensitive)
Misal : [(‘name’, ‘=ilike’, ‘dog’)], maka akan mencari name ‘dog’, ‘Dog’, ‘DoG’, dll. Jika pada inputan ditulis %dog% maka akan mencari name ‘dog’, ‘doggy’, ‘doGgy’, ‘bulldog’, ‘BulldoG’

in sama dengan salah satu dari list value (value harus list dan case sensitive)

not in tidak sama dengan semua list value (value harus list dan case sensitive)

child_of merupakan turunan dari value. Biasanya pada model yang mempunyai attribute _parent_name (bentuk hirarki). Salah santu contoh model hirarki adalah product.category dan stock.location

  • value (type variable, yang bisa berisi str, int, dll) : yang akan dicocokkan dengan nama field

Method Decorators

Penulisan method bisa menggunakan decorator ataupun tidak. Decorator ditulis dengan awalan @ dan terdiri dari beberapa jenis API, sebagai berikut :

  • @api.multi : method berisi recordset. Artinya self bisa berisi lebih dari satu record
  • @api.one : method hanya berisi satu record
  • @api.model : self tidak mempunyai ids karena record belum disimpan ke database
  • @api.depends(‘nama_field1′,’nama_field2’) : merupakan daftar field yang dapat men trigger method. Artinya jika field-field yang ditulis di depends ini valuenya berubah maka method akan dijalankan/compute. Biasanya dipakai pada field compute
  • @api.constrains(‘nama_field1′,’nama_field2’) : hampir sama dengan api.depends, fungsinya untuk melakukan pengecekan validasi data
  • @api.onchange(‘nama_field1′,’nama_field2’) : triggernya hampir sama seperti api.depends, fungsinya untuk memberikan value field, domain atau memberikan warning
  • dll

Praktek

Method def get_sequence pada addons ms_base diperlukan untuk penomoran otomatis sesuai format tertentu. Method ini akan dipanggil di model lain, khususnya di method create (karena penomoran akan muncul otomatis saat record transaksi di save)

def _get_usia pada file ms_pemeriksaan merupakan method yang dipakai pada field compute/function usia, menghitung usia dari tanggal kelahiran pasien. Method ini menggunakan api.one karena untuk menghitung usia pasti hanya dari satu record. Sebenarnya bisa juga menggunakan api.multi asal self nya di looping dulu

override def create untuk mengisi value field name dengan penomoran otomatis. Saat override existing method kita harus menjalankan super(nama_class, self).nama_method(parameter_jika_ada) agar action yang ada pada method asalnya juga dijalankan. Jika kita override atau menulis ulang method tanpa menjalankan super maka method aslinya atau method di parent modelnya tidak akan dijalankan

pada method def action_confirm dilakukan looping pada self karena seperti yang dijelaskan di atas action_confirm menggunakan api.multi sehingga self bisa berisi multi record. Method ini ditrigger oleh button Confirm yang nama button nya sama dengan nama method. Saat button Confirm di klik maka dalam method menjalankan action untuk mengubah state dari Draft menjadi Confirmed dan membuat satu record pemeriksaan

Terimakasih, semoga bermanfaat dan CMIIW…

Tutorial odoo/openerp Indonesia


Belajar Odoo Untuk Pemula [Part 6/8]

Meskipun sudah membuat class, object dan field, itu belum cukup untuk menampilkan field-field yang kita buat pada form aplikasi. Maka langkah selanjutnya kita harus membuat view, action dan menu di xml, dan juga membuat report untuk mengekspor data ke dalam pdf.

Ada beberapa view yang bisa kita buat pada file xml, namun ada 3 view yang paling sering digunakan, yaitu :

  • Search view
<record id="view_ms_pendaftaran_search" model="ir.ui.view">
    <field name="name">ms.pendaftaran.search</field>
    <field name="model">ms.pendaftaran</field>
    <field name="arch" type="xml">
        <search string="Search Pendaftaran">
            <field name="name"/>
            <field name="pasien_id"/>
            <field name="poli_id"/>
            <field name="state"/>
            <filter string="Draft" name="draft" domain="[('state','=','draft')]"/>
            <filter string="Confirm" name="confirm" domain="[('state','=','confirm')]"/>
            <group expand="0">
                <filter name="group_pasien" string="Pasien" domain="[]" context="{'group_by':'pasien_id'}"/>
                <filter name="group_poli" string="Poli" domain="[]" context="{'group_by':'poli_id'}"/>
            </group>
        </search>
    </field>
</record>

  • Tree view
<record model="ir.ui.view" id="ms_pendaftaran_tree_view">
    <field name="name">ms.pendaftaran.tree</field>
    <field name="model">ms.pendaftaran</field>
    <field name="arch" type="xml">
        <tree string="Pendaftaran">
            <field name="name"/>
            <field name="tanggal"/>
            <field name="pasien_id"/>
            <field name="poli_id"/>
            <field name="state"/>
        </tree>
    </field>
</record>

  • Form view
<record model="ir.ui.view" id="ms_pendaftaran_form_view">
    <field name="name">ms.pendaftaran.form</field>
    <field name="model">ms.pendaftaran</field>
    <field name="arch" type="xml">
        <form string="Pendaftaran">
            <header>
                <button name="action_confirm" string="Confirm" type="object" class="oe_highlight" attrs="{'invisible': [('state','!=','draft')]}"/>
                <button name="action_cancel" string="Cancel" type="object" attrs="{'invisible': [('state','=','cancel')]}"/>
                <field name="state" widget="statusbar" statusbar_visible="draft,confirm"/>
            </header>
            <sheet>
                <div class="oe_title">
                    <h1>
                        <field name="name" class="oe_inline" readonly="1"/>
                    </h1>
                </div>
                <group col="4">
                    <field name="pasien_id" attrs="{'readonly': [('state','!=','draft')]}" required="1" options="{'no_open': True, 'no_create': True}"/>
                    <field name="poli_id" attrs="{'readonly': [('state','!=','draft')]}" required="1" options="{'no_open': True, 'no_create': True}"/>
                    <field name="tanggal" attrs="{'readonly': [('state','!=','draft')]}" required="1"/>
                </group>
                <notebook>
                    <page string="Note">
                        <group>
                            <field name="note" nolabel="1" class="oe_inline" placeholder="Note"/>
                        </group>
                    </page>
                    <page string="Audit Trail">
                        <group>
                            <group>
                                <field name="create_uid" readonly="1"/>
                                <field name="create_date" readonly="1"/>
                            </group>
                            <group>
                                <field name="write_uid" readonly="1"/>
                                <field name="write_date" readonly="1"/>
                            </group>
                        </group>
                    </page>
                </notebook>
            </sheet>
        </form>
    </field>
</record>

Satu object bisa memiliki beberapa view, meskipun jenis view nya sama. Misalnya object res.partner memiliki view di form dokter dan form pasien. Jadi data pasien dan dokter akan masuk ke table yang sama yaitu res_partner, meskipun tampilan di aplikasinya terpisah.

Sebagian besar attribute field bisa ditulis di py maupun xml. Perbedaannya, ketika diterapkan di py maka akan berlaku untuk semua view. Tapi ketika ditulis di xml maka hanya berlaku untuk view-view tertentu. Misalnya di object res.partner ada field kode, kemudian saya ingin menambahkan attribute required di py nya, maka baik form dokter maupun pasien wajib mengisi field kode tersebut. Berbeda halnya jika attrbute required tersebut ditambahkan di form view dokter, maka required itu hanya berlaku di form dokter sementara di form pasien tidak.

Kemudian membuat action seperti :

<record model="ir.actions.act_window" id="ms_pendaftaran_action">
    <field name="name">Pendaftaran</field>
    <field name="res_model">ms.pendaftaran</field>
    <field name="view_type">form</field>
    <field name="view_mode">tree,form</field>
    <field name="context">{}</field>
    <field name="domain">[]</field>
</record>
 
<record id="ms_pendaftaran_action_tree" model="ir.actions.act_window.view">
    <field eval="1" name="sequence"/>
    <field name="view_mode">tree</field>
    <field name="view_id" ref="ms_pendaftaran_tree_view"/>
    <field name="act_window_id" ref="ms_pendaftaran_action"/>
</record>
 
<record id="ms_pendaftaran_action_form" model="ir.actions.act_window.view">
    <field eval="2" name="sequence"/>
    <field name="view_mode">form</field>
    <field name="view_id" ref="ms_pendaftaran_form_view"/>
    <field name="act_window_id" ref="ms_pendaftaran_action"/>
</record>

Di action kita bisa menambahkan domain/filter, sehingga hanya record-record tertentu sesuai filter yang akan muncul. Misalnya yang diterapkan pada action dokter dan pasien, meskipun data di tablenya bercampur, tapi data yang ditampilkan di menu nya terpisah.

dan menu seperti :

<menuitem action="ms_pendaftaran_action" id="ms_pendaftaran_menu" parent="ms_base.ms_transaksi_submenu" sequence="10"/>

Kemudian berikut ini contoh penulisan qweb report untuk membuat resep obat seperti yang ada pada addons puskesmas :

<odoo>
    <data>
        <template id="report_resepobat_document">
            <t t-call="report.external_layout">
                <t t-set="o" t-value="o.with_context({})"/>
                <div class="page">
                    <div class="oe_structure"/>
                    <h1 class="text-center">Resep Obat</h1>
                    <div class="row mt32 mb32">
                        <div t-if="o.name" class="col-xs-3">
                            <strong>Nomor :</strong>
                            <p t-field="o.name"/>
                        </div>
                        <div t-if="o.pasien_id" class="col-xs-3">
                            <strong>Pasien :</strong>
                            <p t-field="o.pasien_id.name"/>
                        </div>
                        <div t-if="o.tanggal" class="col-xs-3">
                            <strong>Tanggal :</strong>
                            <p t-field="o.tanggal"/>
                        </div>
                    </div>
                    <table class="table table-condensed">
                        <thead>
                            <tr>
                                <th><strong>Obat</strong></th>
                                <th><strong>Aturan Minum</strong></th>
                                <th><strong>Sebelum/Sesudah Makan</strong></th>
                                <th><strong>Quantity</strong></th>
                                <th><strong>Satuan</strong></th>
                            </tr>
                        </thead>
                        <tbody>
                            <tr t-foreach="o.resep_line" t-as="l">
                                <td>
                                    <span t-field="l.product_id.name"/>
                                </td>
                                <td>
                                    <span t-field="l.aturan_minum"/>
                                </td>
                                <td>
                                    <span t-field="l.waktu_minum"/>
                                </td>
                                <td>
                                    <span t-field="l.qty"/>
                                </td>
                                <td>
                                    <span t-field="l.satuan_id.name"/>
                                </td>
                            </tr>
                        </tbody>
                    </table>
                    <div class="oe_structure"/>
                    <div class="row" name="ttd" style="padding-top:20px;">
                        <div class="col-xs-4" style="text-align:center">
                            <div style="padding-bottom:60px;">Tertanda,</div>
                            <div> ( <span t-field="user.name"/> )</div>
                        </div>
                    </div>
                </div>
            </t>
        </template>
        <template id="report_resepobat">
            <t t-call="report.html_container">
                <t t-foreach="docs" t-as="o">
                    <t t-call="ms_puskesmas.report_resepobat_document"/>
                </t>
            </t>
        </template>
        <report 
            string="Resep Obat" 
            id="cpl_action_report_resepobat" 
            model="ms.pemeriksaan" 
            report_type="qweb-pdf" 
            name="ms_puskesmas.report_resepobat" 
            file="ms_puskesmas.report_resepobat" 
        />
    </data>
</odoo>

Praktek

Penulisan top menu dan group menu pada file ms_base/views/ms_menu.xml

Jika kita mendeklarasikan menuitem tanpa mempunyai attribute parent maka menu tersebut akan menjadi top menu. Kemudian jika ada menuitem yang parent nya ke top menu maka akan jadi group menu (yang terletak sebah kiri). Kemudian jika ada menuitem yang parent nya ke group menu (level ke tiga) maka akan menjadi menu yang terletak di bawah group menu. Selanjutnya jika ada menuitem baru yang parentnya ke menu level ke tiga tersebut maka akan menjadi dropdown, dan begitu selanjutnya.

Setiap menuitem bisa ditambakan action untuk menampilkan form ataupun tidak. Pada umumnya action akan ditambahkan pada menuitem level ke tiga, tapi meskipun begitu action tetap bisa ditambahkan pada menuitem pertama atau kedua. Selama rangkaian menu belum mempunyai action maka menu-menu tersebut tidak akan ditampilkan. Misalnya menu-menu di atas yang terletak pada modul ms_base tidak akan ditampilkan selama kita belum menginstal modul ms_puskesmas, karena action baru ditambahkan pada menuitem di modul ms_puskesmas yang parent nya ke menuitem di ms_base.

Penginputan pada umumnya dilakukan pada form view. Namun pada beberapa object yang hanya memiliki sedikit field seperti object ms.poli, penginputan dilakukan pada tree view. Untuk melakukan itu maka harus menambahkan attribute editable (top/bottom) pada blok tree seperti tree view poli di atas.

Ada 3 hal yang bisa kita tambahkan pada search view :

  • Nama field, fungsinya jika didefinisikan maka ketika kita mengetikkan sesuatu pada kolom search maka akan tampil saran beberapa field yang akan dipakai untuk melakukan filter.
  • Filter, fungsinya untuk melakukan filter otomatis sesuai dengan kriteria yang kita inginkan. Daftar filter ada di bawah kolom search, jika diklik maka data akan difilter sesuai kriteria yang sudah ditentukan. Jika filter tidak muncul seperti ini

maka klik advance search (icon kaca pembesar di sebelah kanan kolom search)

  • Group, fungsinya untuk mengelompokkan data secara otomatis berdasarkan field tertentu, cara penggunaannya hampir sama seperti filter. Harap diingat bahwa field yang bisa dipakai untuk grouping hanya field yang disimpan ke database.

Baik filter ataupun group sama-sama bisa ditambahkan manual lewat aplikasi. Namun akan lebih cepat kalau kita membuatnya di coding

Di sebelah kanan Group by ada Favorites, fungsinya untuk menyimpan current filter atau group bahkan kombinasi dari keduanya. Jadi jika suatu saat kita ingin melakukan filter dan/atau group yang sama maka tinggal klik Favorites yang sudah disimpan tersebut.

pada blok action kita bisa melakukan filter data yang akan ditampilkan, misal pada action dokter di atas data difilter hanya yang field dokternya True

pada context ‘default_dokter’:True fungsinya sama dengan attribute default field pada py. Bedanya kalau kita set default di py maka akan berlaku untuk semua object res.partner, sementara jika set di action xml maka hanya berlaku untuk form/action tertentu. Format penulisannya harus diawali dengan default, jadi default_nama_field.

search_view_id bisa ditambahkan bisa juga tidak. Tujuannya ditambahkan adalah jika kita mempunyai lebih dari satu search view untuk model yang sama maka harus menentukan search view mana yang akan dipakai di action tersebut. Dan jika hanya mempunyai satu search view maka tidak perlu menambahkan penulisan search_view_id. Tapi untuk jaga-jaga apabila nanti ada penambahan search view baru maka saran saya ditambahkan saja search_view_id tersebut.

Kemudian jika ada pertanyaan bagaimana jika tidak menambahkan search_view_id padahal kita mempunyai lebih dari satu search view? Maka jawabannya adalah akan menggunakan salah satu search view yang ditambahkan terakhir atau search view dengan sequence paling kecil. Kalau anda lihat view (baik search view, tree view, form view, dll) ada beberapa yang memakai parameter sequence, tujuannya adalah untuk penggunaan tersebut.

ms_dokter_action_tree dan ms_dokter_action_form fungsinya hampir sama dengan search_view_id, yaitu untuk menggunakan form/tree view tertentu secara spesifik. Jadi kita tidak harus selalu menambahkan kedua action tersebut.

Saat klik refund invoice maka akan muncul tampilan seperti di atas, tampilan tersebut dinamakan wizard.

Untuk form Pembayaran seluruhnya menggunakan bawaan odoo termasuk wizard ini, kita hanya menambahkan menunya saja. Kalau dicek nama model nya wizard ini menggunakan model “account.invoice.refund”.

Ada perbedaan antara model wizard dengan model lainnya, perbedaan tersebut adalah pada model wizard biasanya class nya menggunakan models.TransientModel. Dan perbedaan antara models.TransientModel dengan models.Model adalah pada models.TransientModel data yang disimpan di database bersifat sementara. Jadi apapun yang diinput di sana hanya akan disimpan sementara, dan akan dihapus otomatis setelah beberapa lama.

Print-an resep obat menggunakan qweb report yang cara pembuatannya bisa anda lihat pada file ms_puskesmas/report/report_resepobat.xml

Terimakasih, semoga bermanfaat dan CMIIW…

Tutorial odoo/openerp Indonesia


Belajar Odoo Untuk Pemula [Part 5/8]

Model/object akan menjadi table di database dan kolomnya adalah fields. Tapi tidak semua field akan menjadi kolom, ada beberapa jenis field yang hanya tampil di aplikasi tapi tidak disimpan ke database (isinya merupakan hasil compute dari field lain). Cara penulisan fields silahkan lihat di addons puskesmas. Jenis-jenis field di odoo diantaranya :

  • Char : jenis inputan char
  • Boolean : jenis inputan True/False (default=False)
  • Integer : jenis inputan number (tanpa decimal)
  • Float : jenis inputan decimal
  • Text : jenis inputan char, bedanya dengan field Char adalah kolomnya lebih lebar
  • Selection : jenis inputan pilihan, data yang dipilih bersifat statis ditulis dalam coding
  • Html : hampir sama dengan field Text, bedanya kolom field ini menerapkan kode html
  • Date : jenis inputan tanggal
  • Datetime : jenis inputan tanggal dan waktu
  • Many2one : jenis inputan pilihan, data yang dipilih bersifat dinamis berdasarkan jumlah record di object lain (relasi ke object lain)
  • One2many : merupakan kebalikan dari Many2one, jika dari object A ada relasi Many2one ke object B, maka di object B dapat dibuat relasi One2many ke object A. Dan record yang dimunculkan di object B adalah sebanyak record A yang berelasi ke B.
  • Many2many : relasi banyak ke banyak

Jenis-jenis field di atas hampir semuanya ada pada addons puskesmas, silahkan diexplore.

Attribute Fields

Seperti halnya model, maka field juga memiliki beberapa attribute, diantaranya :

  • string : nama field saat tampil di view
  • required : (default: False) jika True maka field wajib diisi. Dan di database akan membuat kolom jadi not null
  • help : informasi mengenai fungsi dari field tersebut
  • index : membuat index di database
  • readonly : (default: False) jika True maka field tidak dapat diedit
  • default : default value field ketika pertama kali klik tombol create
  • states : field akan muncul ketika state tertentu sesuai yang ditulis di sini, dan akan di hide jika state tidak sama dengan yang didefinisikan di sini
  • groups : field hanya akan muncul di grup user tertentu (hak akses)
  • copy : (default: True) jika record di duplicate maka isi field pada record baru akan sama dengan field pada record yg di duplicate
  • compute : value field diisi oleh function (tidak diisi manual). Field yang memiliki attribute ini secara default tidak akan disimpan ke database
  • related : value field berdasarkan relasi ke field lain. Field yang memiliki attribute ini secara default tidak akan disimpan ke database
  • store : (default: False) dipakai pada field yang menggunakan related atau compute. Fungsinya untuk menyimpan field tersebut ke database (jika value nya diset True)
  • digits : mengatur format decimal pada field float
  • size : mengatur panjang karakter pada field char
  • domain : dipakai pada field Many2one dan One2many untuk memfilter data
  • comodel_name : nama object relasi pada field One2many
  • translate : (default: False) mengaktifkan mode translate pada field
  • attrs : dapat menerapkan attribute invisible, required, readonly berdasarkan kondisi field lain
  • dll

Ada beberapa field yang akan terbuat otomatis setiap kita mendefinisikan object baru dan juga value nya akan terisi otomatis, yaitu :

  • id : format integer, nomor database unique (auto increment)
  • create_date : format datetime, tanggal create record
  • create_uid : format many2one, id user yang create record
  • write_date : format datetime, tanggal edit record terakhir
  • write_uid : format many2one, id user yang edit record terakhir

Field-field tersebut yang ditampilkan di beberapa form puskesmas, pada tab Audit Trail

Jika field-field tersebut dirasa tidak perlu maka bisa dihilangkan (kecuali id) dengan cara menonaktifkan _log_access seperti yang sudah dijelaskan di part sebelumnya.

Praktek

field name diset default=’/’ dan field tanggal default=fields.Datetime.now(), artinya ketika kita akan membuat record maka kedua field tersebut sudah terisi nilainya sebelum kita melakukan input.

pasien_id many2one ke res.partner dengan domain pasien=True, artinya field pasien_id akan menampilkan record yang ada pada object/table res.partner tapi hanya yang field pasien=True, karena di table res_partner ini berisi gabungan data pasien dan dokter. Perlu diperhatikan bahwa setiap ada field yang relasi many2one ke object lain maka :

  • object tersebut harus dideklarasikan di modul yang sama, atau
  • jika beda modul maka di modul yang berisi field many2one tersebut harus depends ke modul yang mendeklarasikan object tersebut. Jika tidak, maka akan terjadi error saat kita melakukan input field yang bersangkutan

field html dapat dilihat hasilnya pada tab Note di form pendaftaran. Pada field html kita bisa melakukan format terhadap teks yang diinput, attach gambar, menambahkan table, dls.

field pasien_id di object ms.pemeriksaan merupakan salah satu contoh field yang tidak akan disimpan ke database. Untuk membuktikannya silahkan buka pg admin dan cari kolom pasien_id di table ms_pemeriksaan, pasti tidak akan ketemu. Kalau anda bertanya jika field pasien_id tidak disimpan ke database, lalu dari mana value nya didapat? Maka jawabannya adalah dengan mengambil value dari table ms_pendaftaran melalui relasi pendaftaran_id, itulah fungsi dari attribute related.

tipe field lain yang juga tidak disimpan ke database adalah field yang mempunyai attribute compute seperti field usia. Berbeda dengan related yang mengambil value melalui relasi field lain, field compute valuenya bisa berdiri sendiri. Jadi kita bisa leluasa menentukan value pada method yang dipanggil di compute tersebut. Attribute compute ini bisa dipakai di field apapun, dengan assign value pada method sesuai dengan tipe field masing-masing.

field related dan compute tersebut secara default memang tidak akan disimpan ke database. Namun jika karna alasan tertentu anda ingin menyimpannya maka bisa dengan menambahkan attribute store=True.

field resep_line merupakan one2many yang berelasi ke ms.resep.obat. Yang harus diperhatikan, setiap kita menambahkan field one2many maka harus ada field many2one dari object relasi ke object field one2many. Misalnya dalam field resep_line (object ms.pemeriksaan) one2many ke ms.resep.obat, maka di object ms.resep.obat harus menambahkan field pemeriksaan_id (many2one ke ms.pemeriksaan) yang kemudian field pemeriksaan_id ini ditulis sebagai attribute inverse_name di field one2many resep_line.

Note : inverse_name merupakan salah satu attribute pada one2many, attribute ini bisa ditulis nama attributenya bisa juga tidak.

contoh :

resep_line = fields.One2many(comodel_name='ms.resep.obat', inverse_name='pemeriksaan_id', 'Resep Obat')

bisa ditulis jadi :

resep_line = fields.One2many('ms.resep.obat', 'pemeriksaan_id', 'Resep Obat')

Terimakasih, semoga bermanfaat dan CMIIW…

Tutorial odoo/openerp Indonesia