اپنی مرضی کے مطابق اوڈو ماڈیولز بنانا: ڈویلپر ٹیوٹوریل
Odoo کا ماڈیول سسٹم ERP دنیا میں سب سے طاقتور توسیعی فریم ورک میں سے ایک ہے۔ Odoo میں ہر فیچر — اکاؤنٹنگ سے لے کر انوینٹری تک CRM تک — ایک ماڈیول ہے۔ اس کا مطلب یہ ہے کہ اپنی مرضی کے مطابق فنکشنلٹی بنانا اوڈو کے اپنے ڈویلپرز کے استعمال کردہ بالکل اسی پیٹرن کی پیروی کرتا ہے، جس سے آپ کو بنیادی فریم ورک تک رسائی حاصل ہوتی ہے۔
اس ٹیوٹوریل میں ایک حسب ضرورت Odoo 19 ماڈیول کے مکمل لائف سائیکل کا احاطہ کیا گیا ہے: ڈائرکٹری کے ڈھانچے کو سہاروں میں ڈھالنے اور ماڈلز کی وضاحت کرنے سے لے کر آراء بنانے، رسائی کو محفوظ بنانے، اور پروڈکشن میں تعیناتی تک۔ آخر تک، آپ کے پاس ایک ورکنگ ماڈیول ہوگا جو Odoo 19 انٹرپرائز کنونشنز کی پیروی کرتا ہے اور بازار کے لیے تیار ہے۔
اہم ٹیک ویز
- ہر Odoo ماڈیول
__manifest__.pyڈسکرپٹر کے ساتھ ایک Python پیکیج ہے۔- ماڈلز
models.Modelسے وراثت میں آتے ہیں اور براہ راست PostgreSQL ٹیبل پر نقشہ بناتے ہیں- XML میں آراء کی وضاحت کی گئی ہے اور نام کے لحاظ سے ماڈل فیلڈز کا حوالہ دیا گیا ہے۔
- سیکورٹی کو
ir.model.accessCSV اور ریکارڈ کے قواعد کے ذریعے نافذ کیا جاتا ہے۔- وزرڈز (
TransientModel) کثیر مرحلہ صارف کے تعاملات کو ہینڈل کرتے ہیں۔- کمپیوٹیڈ فیلڈز اور تبدیلی کے طریقے متعلقہ فیلڈز کو متحرک طور پر اپ ڈیٹ کرتے ہیں۔
- خودکار کارروائیاں اور طے شدہ ملازمتیں محرکات پر سرور سائیڈ منطق چلاتی ہیں۔
- ماڈیول انحصار درست لوڈ آرڈر اور فیچر کی دستیابی کو یقینی بناتا ہے۔
ماڈیول کی ساخت اور سہاروں
ہر Odoo ماڈیول ایک مخصوص ڈھانچہ کے ساتھ ایک ڈائریکٹری ہے۔ بوائلر پلیٹ بنانے کے لیے اوڈو کی بلٹ ان سکیفولڈ کمانڈ استعمال کریں:
# From your Odoo addons directory
python odoo-bin scaffold my_module /path/to/addons
یہ پیدا کرتا ہے:
my_module/
├── __init__.py
├── __manifest__.py
├── models/
│ ├── __init__.py
│ └── my_model.py
├── views/
│ └── my_model_views.xml
├── security/
│ ├── ir.model.access.csv
│ └── my_module_security.xml
├── data/
│ └── my_module_data.xml
├── wizard/
│ └── my_wizard.py
├── report/
│ └── my_report.xml
└── static/
└── src/
└── js/
مینی فیسٹ فائل (__manifest__.py)
{
'name': 'My Custom Module',
'version': '19.0.1.0.0',
'summary': 'Short description for module list',
'description': """
Extended description of what this module does.
Can be multi-line RST text.
""",
'author': 'ECOSIRE Private Limited',
'website': 'https://ecosire.com',
'category': 'Sales/CRM',
'depends': ['sale', 'account', 'stock'],
'data': [
'security/ir.model.access.csv',
'security/my_module_security.xml',
'data/my_module_data.xml',
'views/my_model_views.xml',
'views/menu_views.xml',
'report/my_report.xml',
'wizard/my_wizard_views.xml',
],
'assets': {
'web.assets_backend': [
'my_module/static/src/js/my_widget.js',
'my_module/static/src/css/my_styles.css',
],
},
'license': 'OPL-1',
'installable': True,
'application': False,
'auto_install': False,
'price': 249.0,
'currency': 'USD',
}
ورژن نمبرنگ کنونشن: {odoo_version}.{major}.{minor}.{patch}۔ نئے ماڈیولز کے لیے ہمیشہ 19.0.1.0.0 سے شروع کریں۔
ماڈلز کی وضاحت کرنا
ماڈلز کسی بھی Odoo ماڈیول کا دل ہوتے ہیں۔ وہ ڈیٹا کی ساخت اور کاروباری منطق کی وضاحت کرتے ہیں۔
# models/service_request.py
from odoo import api, fields, models
from odoo.exceptions import ValidationError, UserError
class ServiceRequest(models.Model):
_name = 'my.service.request'
_description = 'Service Request'
_inherit = ['mail.thread', 'mail.activity.mixin']
_order = 'date_request desc, name'
_rec_name = 'name'
name = fields.Char(
string='Reference',
required=True,
copy=False,
readonly=True,
default=lambda self: self.env['ir.sequence'].next_by_code('my.service.request')
)
state = fields.Selection([
('draft', 'Draft'),
('submitted', 'Submitted'),
('in_progress', 'In Progress'),
('done', 'Completed'),
('cancelled', 'Cancelled'),
], string='Status', default='draft', tracking=True)
partner_id = fields.Many2one(
'res.partner', string='Customer',
required=True, tracking=True,
domain=[('customer_rank', '>', 0)]
)
user_id = fields.Many2one(
'res.users', string='Assigned To',
default=lambda self: self.env.user
)
date_request = fields.Datetime(
string='Request Date',
default=fields.Datetime.now,
required=True
)
date_deadline = fields.Date(string='Deadline')
description = fields.Html(string='Description')
priority = fields.Selection([
('0', 'Normal'),
('1', 'Low'),
('2', 'High'),
('3', 'Urgent'),
], string='Priority', default='0')
tag_ids = fields.Many2many(
'my.service.tag', string='Tags'
)
line_ids = fields.One2many(
'my.service.request.line', 'request_id',
string='Service Lines'
)
amount_total = fields.Float(
string='Total Amount',
compute='_compute_amount_total',
store=True
)
company_id = fields.Many2one(
'res.company', string='Company',
required=True,
default=lambda self: self.env.company
)
@api.depends('line_ids.subtotal')
def _compute_amount_total(self):
for request in self:
request.amount_total = sum(request.line_ids.mapped('subtotal'))
@api.constrains('date_deadline', 'date_request')
def _check_deadline(self):
for record in self:
if record.date_deadline and record.date_request:
if record.date_deadline < record.date_request.date():
raise ValidationError("Deadline cannot be before the request date.")
@api.onchange('partner_id')
def _onchange_partner_id(self):
if self.partner_id:
self.user_id = self.partner_id.user_id or self.env.user
def action_submit(self):
for record in self:
if not record.line_ids:
raise UserError("Cannot submit a request without service lines.")
record.state = 'submitted'
record.message_post(body="Service request submitted for processing.")
def action_start_progress(self):
self.write({'state': 'in_progress'})
def action_mark_done(self):
self.write({'state': 'done'})
def action_cancel(self):
for record in self:
if record.state == 'done':
raise UserError("Cannot cancel a completed request.")
record.state = 'cancelled'
@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(
'my.service.request'
) or 'New'
return super().create(vals_list)
سروس ریکوئسٹ لائن ماڈل:
class ServiceRequestLine(models.Model):
_name = 'my.service.request.line'
_description = 'Service Request Line'
request_id = fields.Many2one(
'my.service.request', string='Request',
required=True, ondelete='cascade'
)
product_id = fields.Many2one(
'product.product', string='Service',
required=True,
domain=[('type', '=', 'service')]
)
description = fields.Text(string='Description')
quantity = fields.Float(string='Quantity', default=1.0)
price_unit = fields.Float(string='Unit Price')
subtotal = fields.Float(
string='Subtotal',
compute='_compute_subtotal',
store=True
)
@api.depends('quantity', 'price_unit')
def _compute_subtotal(self):
for line in self:
line.subtotal = line.quantity * line.price_unit
@api.onchange('product_id')
def _onchange_product_id(self):
if self.product_id:
self.price_unit = self.product_id.lst_price
self.description = self.product_id.description_sale
مناظر تخلیق کرنا
ملاحظات اس بات کی وضاحت کرتے ہیں کہ UI میں ریکارڈ کیسے دکھائے جاتے ہیں۔ Odoo فارمز، فہرستوں، کنبان بورڈز اور مزید کی وضاحت کے لیے XML کا استعمال کرتا ہے۔
<!-- views/service_request_views.xml -->
<odoo>
<!-- Form View -->
<record id="view_service_request_form" model="ir.ui.view">
<field name="name">my.service.request.form</field>
<field name="model">my.service.request</field>
<field name="arch" type="xml">
<form string="Service Request">
<header>
<button name="action_submit" string="Submit"
type="object" class="oe_highlight"
invisible="state != 'draft'"/>
<button name="action_start_progress" string="Start"
type="object" class="oe_highlight"
invisible="state != 'submitted'"/>
<button name="action_mark_done" string="Mark Done"
type="object" class="oe_highlight"
invisible="state != 'in_progress'"/>
<button name="action_cancel" string="Cancel"
type="object"
invisible="state in ['done', 'cancelled']"/>
<field name="state" widget="statusbar"
statusbar_visible="draft,submitted,in_progress,done"/>
</header>
<sheet>
<div class="oe_title">
<h1>
<field name="name" readonly="1"/>
</h1>
</div>
<group>
<group>
<field name="partner_id"
options="{'no_create': True}"/>
<field name="user_id"/>
<field name="priority" widget="priority"/>
</group>
<group>
<field name="date_request"/>
<field name="date_deadline"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
</group>
<field name="tag_ids" widget="many2many_tags"/>
<notebook>
<page string="Service Lines">
<field name="line_ids">
<tree editable="bottom">
<field name="product_id"/>
<field name="description"/>
<field name="quantity"/>
<field name="price_unit"/>
<field name="subtotal" readonly="1"/>
</tree>
</field>
<group class="oe_subtotal_footer">
<field name="amount_total"
widget="monetary"
class="oe_subtotal_footer_separator"/>
</group>
</page>
<page string="Description">
<field name="description" widget="html"
placeholder="Detailed description..."/>
</page>
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids"/>
<field name="activity_ids"/>
<field name="message_ids"/>
</div>
</form>
</field>
</record>
<!-- List View -->
<record id="view_service_request_tree" model="ir.ui.view">
<field name="name">my.service.request.list</field>
<field name="model">my.service.request</field>
<field name="arch" type="xml">
<tree string="Service Requests" decoration-danger="state=='cancelled'"
decoration-success="state=='done'">
<field name="name"/>
<field name="partner_id"/>
<field name="user_id" optional="show"/>
<field name="priority" widget="priority"/>
<field name="date_request"/>
<field name="date_deadline" optional="show"/>
<field name="amount_total" sum="Total"/>
<field name="state" widget="badge"
decoration-info="state=='draft'"
decoration-warning="state=='submitted'"
decoration-primary="state=='in_progress'"
decoration-success="state=='done'"
decoration-danger="state=='cancelled'"/>
</tree>
</field>
</record>
<!-- Search View -->
<record id="view_service_request_search" model="ir.ui.view">
<field name="name">my.service.request.search</field>
<field name="model">my.service.request</field>
<field name="arch" type="xml">
<search>
<field name="name" string="Reference"/>
<field name="partner_id"/>
<field name="user_id"/>
<filter string="My Requests" name="my_requests"
domain="[('user_id', '=', uid)]"/>
<filter string="In Progress" name="in_progress"
domain="[('state', '=', 'in_progress')]"/>
<filter string="Urgent" name="urgent"
domain="[('priority', '=', '3')]"/>
<separator/>
<filter string="This Month" name="this_month"
domain="[('date_request', '>=',
(context_today() - relativedelta(day=1)).strftime('%Y-%m-%d'))]"/>
<group expand="0" string="Group By">
<filter string="Customer" name="group_partner"
context="{'group_by': 'partner_id'}"/>
<filter string="Status" name="group_state"
context="{'group_by': 'state'}"/>
<filter string="Assigned To" name="group_user"
context="{'group_by': 'user_id'}"/>
</group>
</search>
</field>
</record>
<!-- Action -->
<record id="action_service_request" model="ir.actions.act_window">
<field name="name">Service Requests</field>
<field name="res_model">my.service.request</field>
<field name="view_mode">list,form,kanban</field>
<field name="search_view_id" ref="view_service_request_search"/>
<field name="context">{'search_default_in_progress': 1}</field>
</record>
</odoo>
سیکیورٹی کنفیگریشن
کسی بھی پروڈکشن ماڈیول کے لیے سیکیورٹی لازمی ہے۔
ایکسیس کنٹرول لسٹ (security/ir.model.access.csv):
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_service_request_user,service.request.user,model_my_service_request,base.group_user,1,1,1,0
access_service_request_manager,service.request.manager,model_my_service_request,base.group_system,1,1,1,1
access_service_request_line_user,service.request.line.user,model_my_service_request_line,base.group_user,1,1,1,1
ریکارڈ کے قوانین (security/my_module_security.xml):
<odoo>
<!-- Users can only see their own requests unless they're managers -->
<record id="rule_service_request_own" model="ir.rule">
<field name="name">Service Request: Own Records</field>
<field name="model_id" ref="model_my_service_request"/>
<field name="domain_force">
[('user_id', '=', user.id)]
</field>
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_unlink" eval="False"/>
</record>
</odoo>
وزرڈز (ٹرانسینٹ ماڈل)
وزرڈز گائیڈڈ ملٹی سٹیپ ایکشنز کے لیے عارضی شکلیں ہیں۔
# wizard/service_request_wizard.py
from odoo import api, fields, models
class ServiceRequestBulkAssign(models.TransientModel):
_name = 'my.service.request.bulk.assign'
_description = 'Bulk Assign Service Requests'
user_id = fields.Many2one(
'res.users', string='Assign To', required=True
)
request_ids = fields.Many2many(
'my.service.request', string='Requests',
default=lambda self: self.env.context.get('active_ids', [])
)
note = fields.Text(string='Note')
def action_assign(self):
self.request_ids.write({'user_id': self.user_id.id})
if self.note:
for request in self.request_ids:
request.message_post(body=self.note)
return {'type': 'ir.actions.act_window_close'}
خودکار اعمال اور ترتیب
آٹو نمبرنگ کے لیے ترتیب:
<record id="seq_service_request" model="ir.sequence">
<field name="name">Service Request</field>
<field name="code">my.service.request</field>
<field name="prefix">SRQ/%(year)s/</field>
<field name="padding">5</field>
<field name="company_id" eval="False"/>
</record>
شیڈول کردہ کارروائی (کرون جاب):
# In the model
def _cron_remind_overdue_requests(self):
overdue = self.search([
('state', 'in', ['submitted', 'in_progress']),
('date_deadline', '<', fields.Date.today()),
])
for request in overdue:
request.activity_schedule(
'mail.mail_activity_data_warning',
summary='Overdue Service Request',
user_id=request.user_id.id
)
<record id="ir_cron_remind_overdue" model="ir.cron">
<field name="name">Remind Overdue Service Requests</field>
<field name="model_id" ref="model_my_service_request"/>
<field name="state">code</field>
<field name="code">model._cron_remind_overdue_requests()</field>
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field name="active">True</field>
</record>
اکثر پوچھے گئے سوالات
models.Model, models.TransientModel, اور models.AbstractModel میں کیا فرق ہے؟
models.Model ڈیٹا بیس میں ایک مستقل جدول بناتا ہے۔ models.TransientModel ایک عارضی جدول بناتا ہے جسے وقتاً فوقتاً صاف کیا جاتا ہے (وزرڈز کے لیے استعمال کیا جاتا ہے)۔ models.AbstractModel کوئی ٹیبل نہیں بناتا — یہ ایک ایسا مکس ہے جو دوسرے ماڈلز کو علیحدہ جدول بنائے بغیر طریقے اور فیلڈز حاصل کرنے کے لیے وراثت میں مل سکتے ہیں۔
میں بنیادی کوڈ میں ترمیم کیے بغیر موجودہ Odoo ماڈل کو کیسے بڑھا سکتا ہوں؟
موجودہ ماڈل کے نام کے ساتھ _inherit استعمال کریں اور _name کو چھوڑ دیں۔ یہ آپ کے فیلڈز اور طریقوں کو موجودہ ماڈل میں شامل کرتا ہے: class SaleOrder(models.Model): _inherit = 'sale.order'۔ ایک نیا ماڈل بنانے کے لیے جو دوسرے کے رویے کو نقطۂ آغاز کے طور پر نقل کرتا ہے، _name (نیا نام) اور _inherit (ماخذ ماڈل) دونوں استعمال کریں۔
جب میرے ماڈیول کا ڈیٹا ماڈل تبدیل ہوتا ہے تو میں منتقلی کو کیسے سنبھالوں؟
my_module/migrations/{version}/pre-migrate.py یا post-migrate.py میں منتقلی کا اسکرپٹ بنائیں۔ یہ اسکرپٹ ماڈیول اپ ڈیٹ کے دوران خود بخود چلتی ہیں۔ کالم کے نام بدلنے کے لیے، openupgradelib مددگار استعمال کریں۔ پروڈکشن کے لیے درخواست دینے سے پہلے ہمیشہ پروڈکشن ڈیٹا بیس کی ایک کاپی پر منتقلی کی جانچ کریں۔
کیا میں بنیادی XML فائلوں میں ترمیم کیے بغیر موجودہ Odoo ویوز کو اوور رائیڈ کر سکتا ہوں؟
جی ہاں آپ جس منظر کو بڑھانا چاہتے ہیں اس کا حوالہ دینے کے لیے inherit_id کا استعمال کریں، پھر عنصر کو تلاش کرنے کے لیے xpath اظہار اور position وصف (پہلے، بعد میں، اندر، تبدیل کریں، اوصاف) ترمیم کی وضاحت کے لیے استعمال کریں۔ یہ آپ کی تبدیلیوں کو الگ تھلگ اور اپ گریڈ کرنے کے لیے محفوظ رکھتا ہے۔
میں ملٹی کمپنی ماحول میں فیلڈ کمپنی کے لیے مخصوص کیسے بناؤں؟
فیلڈ کی تعریف پر company_dependent=True استعمال کریں: my_field = fields.Char(company_dependent=True)۔ یہ فی کمپنی ایک الگ قدر ذخیرہ کرتا ہے، لہذا کمپنی A اور کمپنی B ایک ہی ریکارڈ کے لیے مختلف قدریں رکھ سکتے ہیں۔ یہ قیمت کی فہرستوں، ٹیکس اکاؤنٹس، اور کمپنی کے لیے مخصوص کنفیگریشنز کے لیے استعمال ہوتا ہے۔
پیغامات کو لاگ کرنے اور ترقی کے دوران ڈیبگ کرنے کا صحیح طریقہ کیا ہے؟
ازگر کا logging ماڈیول استعمال کریں: import logging; _logger = logging.getLogger(__name__)۔ مختلف شدت کی سطحوں کے لیے _logger.info()، _logger.warning()، _logger.error() استعمال کریں۔ پروڈکشن کوڈ میں کبھی بھی print() بیانات استعمال نہ کریں۔ ترقی میں، تمام ڈیبگ آؤٹ پٹ دیکھنے کے لیے --log-level=debug کے ساتھ Odoo چلائیں۔
اگلے اقدامات
پروڈکشن کے لیے تیار Odoo ماڈیول بنانے کے لیے فریم ورک، PostgreSQL کارکردگی کے تحفظات، اپ گریڈ کے لیے محفوظ نمونوں، اور مکمل جانچ کی گہرائی سے علم کی ضرورت ہوتی ہے۔ Odoo مارکیٹ پلیس کے لیے بنائے گئے ماڈیولز سیکیورٹی، کارکردگی، اور کوڈ کے معیار کے لیے اضافی توثیق سے گزرتے ہیں۔
ECOSIRE مخصوص کاروباری ضروریات کے لیے اپنی مرضی کے مطابق Odoo 19 انٹرپرائز ماڈیولز تیار کرتا ہے — خصوصی صنعت کے ورک فلو سے لے کر مارکیٹ پلیس کنیکٹر ماڈیولز تک۔ ہماری ڈیولپمنٹ ٹیم Odoo کے آفیشل کوڈنگ رہنما خطوط پر عمل کرتی ہے، اس میں جامع یونٹ ٹیسٹ شامل ہیں، اور مکمل دستاویزات کے ساتھ ماڈیول فراہم کرتی ہے۔
ECOSIRE → سے ایک حسب ضرورت اوڈو ماڈیول کمیشن کریں
اپنی ضروریات کا اشتراک کریں اور ہم ترقی کی کوششوں کا دائرہ کار بنائیں گے، ایک ٹائم لائن فراہم کریں گے، اور ایک ماڈیول فراہم کریں گے جو آپ کے Odoo 19 انٹرپرائز کی تنصیب کے ساتھ صاف طور پر مربوط ہو۔
تحریر
ECOSIRE TeamTechnical Writing
The ECOSIRE technical writing team covers Odoo ERP, Shopify eCommerce, AI agents, Power BI analytics, GoHighLevel automation, and enterprise software best practices. Our guides help businesses make informed technology decisions.
متعلقہ مضامین
Odoo Accounting vs QuickBooks: Detailed Comparison 2026
In-depth 2026 comparison of Odoo Accounting vs QuickBooks covering features, pricing, integrations, scalability, and which platform fits your business needs.
Case Study: eCommerce Migration to Shopify with Odoo Backend
How a fashion retailer migrated from WooCommerce to Shopify and connected it to Odoo ERP, cutting order fulfillment time by 71% and growing revenue 43%.
Case Study: Manufacturing ERP Implementation with Odoo 19
How a Pakistani auto-parts manufacturer cut order processing time by 68% and reduced inventory variance to under 2% with ECOSIRE's Odoo 19 implementation.