Nov 27th 2025
This commit is contained in:
parent
20e912c5d9
commit
37a29392e0
@ -1 +1,3 @@
|
||||
from . import models
|
||||
from . import models
|
||||
from . import wizard
|
||||
from . import controllers
|
||||
@ -33,6 +33,11 @@
|
||||
'views/zoo_creature_views.xml',
|
||||
'views/zoo_cage_views.xml',
|
||||
'views/zoo_health_records.xml',
|
||||
'views/zoo_animal_meal_views.xml',
|
||||
'views/zoo_diet_plans.xml',
|
||||
'wizard/toy_add_views.xml',
|
||||
'wizard/cage_update_views.xml',
|
||||
'wizard/animal_feeding_views.xml',
|
||||
'views/zoo_husbandry_task_views.xml'
|
||||
],
|
||||
}
|
||||
Binary file not shown.
1
addons/zoo/controllers/__init__.py
Normal file
1
addons/zoo/controllers/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from . import main
|
||||
BIN
addons/zoo/controllers/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
addons/zoo/controllers/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
addons/zoo/controllers/__pycache__/main.cpython-312.pyc
Normal file
BIN
addons/zoo/controllers/__pycache__/main.cpython-312.pyc
Normal file
Binary file not shown.
33
addons/zoo/controllers/main.py
Normal file
33
addons/zoo/controllers/main.py
Normal file
@ -0,0 +1,33 @@
|
||||
import odoo
|
||||
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
|
||||
from odoo.http import request
|
||||
import datetime
|
||||
import json
|
||||
|
||||
import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
def convert_datetime(d):
|
||||
if d:
|
||||
return d.strftime(DEFAULT_SERVER_DATETIME_FORMAT) if isinstance(d, datetime.datetime) else d.strftime(DEFAULT_SERVER_DATE_FORMAT)
|
||||
else:
|
||||
return False
|
||||
|
||||
class ZooAPI(odoo.http.Controller):
|
||||
@odoo.http.route('/api/zoo/animal/<id>', type='http', auth='none', cors='*', csrf=False)
|
||||
def get_animal_by_id(self, id, **kw):
|
||||
env = request.env
|
||||
id = int(id)
|
||||
model = "zoo.animal"
|
||||
record = env[model].sudo().search([('id', '=', id)])
|
||||
if record:
|
||||
res = {
|
||||
"name": record.name,
|
||||
"dob": convert_datetime(d=record.dob),
|
||||
"gender": record.gender,
|
||||
"feed_time": convert_datetime(d=record.feed_time),
|
||||
}
|
||||
_logger.warning(res)
|
||||
return request.make_json_response(res, status=200)
|
||||
else:
|
||||
return request.make_json_response({}, status=200)
|
||||
21
addons/zoo/data/product_food_import.csv
Normal file
21
addons/zoo/data/product_food_import.csv
Normal file
@ -0,0 +1,21 @@
|
||||
name,default_code,type,list_price,standard_price,uom_id/id,categ_id/id,description
|
||||
Beef (Raw),FOOD001,consu,15.50,12.00,uom.product_uom_kgm,product.product_category_all,Fresh raw beef for carnivores
|
||||
Chicken (Raw),FOOD002,consu,8.75,6.50,uom.product_uom_kgm,product.product_category_all,Fresh raw chicken meat
|
||||
Fish (Whole),FOOD003,consu,12.00,9.00,uom.product_uom_kgm,product.product_category_all,Fresh whole fish for aquatic animals
|
||||
Pork (Raw),FOOD004,consu,10.50,8.00,uom.product_uom_kgm,product.product_category_all,Fresh pork meat
|
||||
Deer Meat,FOOD005,consu,18.00,14.00,uom.product_uom_kgm,product.product_category_all,Premium deer meat for large carnivores
|
||||
Grass Hay,FOOD006,consu,3.50,2.00,uom.product_uom_kgm,product.product_category_all,Dried grass hay for herbivores
|
||||
Alfalfa,FOOD007,consu,4.25,2.50,uom.product_uom_kgm,product.product_category_all,High quality alfalfa
|
||||
Fresh Vegetables,FOOD008,consu,5.00,3.50,uom.product_uom_kgm,product.product_category_all,Mixed fresh vegetables
|
||||
Fruits (Mixed),FOOD009,consu,6.50,4.50,uom.product_uom_kgm,product.product_category_all,Assorted fresh fruits
|
||||
Bamboo,FOOD010,consu,7.00,5.00,uom.product_uom_kgm,product.product_category_all,Fresh bamboo shoots and stalks
|
||||
Fish Pellets,FOOD011,consu,4.50,3.00,uom.product_uom_kgm,product.product_category_all,Specialized fish food pellets
|
||||
Bird Seed Mix,FOOD012,consu,3.00,2.00,uom.product_uom_kgm,product.product_category_all,Mixed seeds for birds
|
||||
Insects (Live),FOOD013,consu,12.50,9.00,uom.product_uom_kgm,product.product_category_all,Live insects for insectivores
|
||||
Rabbit (Whole),FOOD014,consu,9.00,7.00,uom.product_uom_kgm,product.product_category_all,Whole rabbit for medium carnivores
|
||||
Squid,FOOD015,consu,11.00,8.50,uom.product_uom_kgm,product.product_category_all,Fresh squid for marine animals
|
||||
Krill,FOOD016,consu,14.00,11.00,uom.product_uom_kgm,product.product_category_all,Frozen krill for aquatic animals
|
||||
Lettuce,FOOD017,consu,2.50,1.50,uom.product_uom_kgm,product.product_category_all,Fresh lettuce leaves
|
||||
Carrots,FOOD018,consu,2.00,1.20,uom.product_uom_kgm,product.product_category_all,Fresh carrots
|
||||
Sweet Potato,FOOD019,consu,3.00,2.00,uom.product_uom_kgm,product.product_category_all,Fresh sweet potatoes
|
||||
Apples,FOOD020,consu,4.00,3.00,uom.product_uom_kgm,product.product_category_all,Fresh apples
|
||||
|
31
addons/zoo/data/zoo_animal_import.csv
Normal file
31
addons/zoo/data/zoo_animal_import.csv
Normal file
@ -0,0 +1,31 @@
|
||||
name,description,dob,gender,weight,nickname,is_alive,is_purchased,purchase_price,creature_id/name,cage_id/name
|
||||
Simba,Majestic lion with golden mane,2018-05-15,male,190.5,King,True,True,15000.0,Lion,Savanna Habitat
|
||||
Nala,Beautiful lioness and pride leader,2019-03-20,female,130.0,Queen,True,True,12000.0,Lion,Savanna Habitat
|
||||
Shere Khan,Bengal tiger with striking orange stripes,2017-08-10,male,220.0,Khan,True,True,25000.0,Tiger,Rainforest Pavilion
|
||||
Rajah,Playful tiger cub learning to hunt,2022-11-05,female,45.5,Raja,True,False,0.0,Tiger,Rainforest Pavilion
|
||||
Dumbo,Gentle giant elephant with big ears,2015-01-12,male,5400.0,Big Ears,True,True,50000.0,Elephant,Safari Plains
|
||||
Ellie,Young elephant loves to play in water,2020-06-30,female,2800.0,Ellie,True,False,0.0,Elephant,Safari Plains
|
||||
Melman,Tall giraffe with beautiful spots,2016-07-22,male,1200.0,Mel,True,True,18000.0,Giraffe,Savanna Habitat
|
||||
Gloria,Graceful giraffe mother,2018-09-15,female,950.0,Glori,True,True,16000.0,Giraffe,Savanna Habitat
|
||||
Marty,Striped zebra always energetic,2019-04-08,male,350.0,Stripey,True,True,8000.0,Zebra,Safari Plains
|
||||
Zara,Young zebra with perfect stripes,2021-12-20,female,280.0,Z,True,False,0.0,Zebra,Safari Plains
|
||||
Skipper,Penguin leader of the colony,2020-02-14,male,5.5,Skip,True,True,3000.0,Penguin,Penguin Cove
|
||||
Kowalski,Smart penguin loves fish,2020-02-14,male,5.2,Kowl,True,True,3000.0,Penguin,Penguin Cove
|
||||
Private,Young penguin still learning,2021-08-01,male,4.8,Priv,True,False,0.0,Penguin,Penguin Cove
|
||||
Rico,Tough penguin protects the group,2020-02-14,male,5.7,Ric,True,True,3000.0,Penguin,Penguin Cove
|
||||
Aurora,Beautiful white polar bear female,2016-11-30,female,450.0,Rory,True,True,35000.0,Polar Bear,Polar Pavilion
|
||||
Blizzard,Large male polar bear king of arctic,2015-09-18,male,550.0,Bliz,True,True,40000.0,Polar Bear,Arctic Zone
|
||||
Flipper,Friendly dolphin loves to jump,2019-05-25,male,180.0,Flip,True,True,20000.0,Dolphin,Aquatic Center
|
||||
Echo,Dolphin with beautiful voice,2020-07-12,female,165.0,Echi,True,True,18000.0,Dolphin,Tropical Reef
|
||||
Jaws,Impressive great white shark,2014-03-03,male,900.0,Big J,True,True,45000.0,Shark,Aquatic Center
|
||||
Marina,Graceful reef shark,2018-10-20,female,220.0,Mari,True,True,15000.0,Shark,Tropical Reef
|
||||
Freedom,Majestic bald eagle symbol of liberty,2017-06-04,male,6.5,Free,True,True,5000.0,Eagle,Bird Sanctuary
|
||||
Liberty,Female eagle partner of Freedom,2018-04-15,female,5.8,Libby,True,True,4500.0,Eagle,Bird Sanctuary
|
||||
Zeus,Powerful golden eagle,2016-01-20,male,7.2,Z-man,True,True,6000.0,Eagle,Bird Sanctuary
|
||||
Spirit,Young eagle learning to fly,2022-05-10,male,4.5,Spir,True,False,0.0,Eagle,Mountain Ridge
|
||||
Leo,Strong second alpha lion,2019-08-22,male,185.0,Leo,True,True,14000.0,Lion,Savanna Habitat
|
||||
Elsa,Brave lioness hunter,2020-10-11,female,125.0,Ice Queen,True,True,11000.0,Lion,Desert Dome
|
||||
Tony,Young energetic tiger,2021-03-15,male,95.0,T-Rex,True,False,0.0,Tiger,Rainforest Pavilion
|
||||
Shira,Rare beautiful white tiger,2018-12-25,female,140.0,Snow,True,True,30000.0,Tiger,Primate Paradise
|
||||
Trunks,Adorable baby elephant,2023-02-28,male,800.0,Trunk,True,False,0.0,Elephant,Safari Plains
|
||||
Patches,Cute giraffe calf with unique spots,2023-01-05,female,320.0,Patchy,True,False,0.0,Giraffe,Savanna Habitat
|
||||
|
16
addons/zoo/data/zoo_cage_import.csv
Normal file
16
addons/zoo/data/zoo_cage_import.csv
Normal file
@ -0,0 +1,16 @@
|
||||
name,capacity,location,cage_type,area,description,active
|
||||
Savanna Habitat,10,North Wing,outdoor,500.5,Large outdoor habitat for African animals,True
|
||||
Rainforest Pavilion,8,East Wing,indoor,350.0,Climate-controlled rainforest environment,True
|
||||
Arctic Zone,6,South Wing,outdoor,400.0,Cold climate habitat with ice features,True
|
||||
Aquatic Center,15,West Wing,aquarium,600.0,Large aquarium for marine life,True
|
||||
Bird Sanctuary,20,Central Area,aviary,450.0,Open-air aviary with netting,True
|
||||
Desert Dome,5,North Wing,indoor,300.0,Heated desert environment,True
|
||||
Primate Paradise,12,East Wing,outdoor,380.0,Multi-level jungle gym for primates,True
|
||||
Reptile House,10,South Wing,indoor,250.0,Temperature-controlled reptile habitat,True
|
||||
Polar Pavilion,4,West Wing,indoor,320.0,Frozen tundra simulation,True
|
||||
Safari Plains,25,Central Area,outdoor,800.0,Large plains for grazing animals,True
|
||||
Tropical Reef,20,East Wing,aquarium,550.0,Coral reef ecosystem,True
|
||||
Mountain Ridge,8,North Wing,outdoor,420.0,Rocky mountain terrain,True
|
||||
Nocturnal Gallery,6,South Wing,indoor,180.0,Dark environment for nocturnal animals,True
|
||||
Penguin Cove,15,West Wing,outdoor,400.0,Icy pool for penguins,True
|
||||
Butterfly Garden,30,Central Area,aviary,200.0,Glass dome for butterflies,True
|
||||
|
11
addons/zoo/data/zoo_creature_import.csv
Normal file
11
addons/zoo/data/zoo_creature_import.csv
Normal file
@ -0,0 +1,11 @@
|
||||
name,environment,is_rare
|
||||
Lion,ground,False
|
||||
Tiger,forest,True
|
||||
Elephant,ground,False
|
||||
Giraffe,ground,False
|
||||
Zebra,ground,False
|
||||
Penguin,cool,False
|
||||
Polar Bear,cool,True
|
||||
Dolphin,ocean,False
|
||||
Shark,sea,True
|
||||
Eagle,sky,False
|
||||
|
@ -3,4 +3,6 @@ from . import zoo_creature
|
||||
from . import zoo_cage
|
||||
from . import zoo_health_record
|
||||
from . import zoo_diet_plan
|
||||
from . import zoo_diet_line
|
||||
from . import zoo_diet_line
|
||||
from . import zoo_animal_meal
|
||||
from . import zoo_husbandry_task
|
||||
Binary file not shown.
Binary file not shown.
BIN
addons/zoo/models/__pycache__/zoo_animal_meal.cpython-312.pyc
Normal file
BIN
addons/zoo/models/__pycache__/zoo_animal_meal.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
addons/zoo/models/__pycache__/zoo_husbandry_task.cpython-312.pyc
Normal file
BIN
addons/zoo/models/__pycache__/zoo_husbandry_task.cpython-312.pyc
Normal file
Binary file not shown.
@ -8,47 +8,92 @@ class ZooAnimal(models.Model):
|
||||
_name = "zoo.animal"
|
||||
_description = "Animal in the zoo"
|
||||
|
||||
name = fields.Char('Animal Name', required=True)
|
||||
name = fields.Char('Animal Name',
|
||||
required=True)
|
||||
|
||||
description = fields.Text('Description')
|
||||
dob = fields.Date('DOB', required=False)
|
||||
|
||||
dob = fields.Date('DOB',
|
||||
required=False)
|
||||
|
||||
gender = fields.Selection([
|
||||
('male', 'Male'),
|
||||
('female', 'Female')
|
||||
], string='Gender', default='male', required=True)
|
||||
feed_time = fields.Datetime('Feed Time', copy=False)
|
||||
is_alive = fields.Boolean('Is Alive', default=True)
|
||||
image = fields.Binary("Image", attachment=True, help="Animal Image")
|
||||
],
|
||||
string='Gender',
|
||||
default='male',
|
||||
required=True)
|
||||
|
||||
feed_time = fields.Datetime('Feed Time',
|
||||
copy=False)
|
||||
|
||||
is_alive = fields.Boolean('Is Alive',
|
||||
default=True)
|
||||
|
||||
image = fields.Binary("Image",
|
||||
attachment=True,
|
||||
help="Animal Image")
|
||||
|
||||
weight = fields.Float('Weight (kg)')
|
||||
|
||||
weight_pound = fields.Float('Weight (pounds)')
|
||||
|
||||
introduction = fields.Text('Introduction (EN)')
|
||||
|
||||
nickname = fields.Char('Nickname')
|
||||
|
||||
introduction_vn = fields.Html('Introduction (VI)')
|
||||
is_purchased = fields.Boolean('Has Been Purchased', default=False)
|
||||
|
||||
is_purchased = fields.Boolean('Has Been Purchased',
|
||||
default=False)
|
||||
purchase_price = fields.Float('Purchase Price')
|
||||
veterinarian_id = fields.Many2one(comodel_name='res.partner', string='Veterinarian')
|
||||
|
||||
# thêm vào cuối:
|
||||
|
||||
age = fields.Integer('Pet Age', compute='_compute_age')
|
||||
number_of_children = fields.Integer('Number of Children', compute='_compute_number_of_children')
|
||||
|
||||
# thêm các trường quan hệ sau:
|
||||
mother_id = fields.Many2one(comodel_name='zoo.animal', string='Mother', ondelete='set null') # ondelete: 'set null', 'restrict', 'cascade'
|
||||
mother_name = fields.Char('Mother Name', related='mother_id.name')
|
||||
female_children_ids = fields.One2many(comodel_name='zoo.animal', inverse_name='mother_id', string='Female Children')
|
||||
father_id = fields.Many2one(comodel_name='zoo.animal', string='Father', ondelete='set null')
|
||||
father_name = fields.Char('Father Name', related='father_id.name')
|
||||
male_children_ids = fields.One2many(comodel_name='zoo.animal', inverse_name='father_id', string='Male Children')
|
||||
|
||||
veterinarian_id = fields.Many2one(comodel_name='res.partner',
|
||||
string='Veterinarian')
|
||||
|
||||
age = fields.Integer('Pet Age',
|
||||
compute='_compute_age')
|
||||
|
||||
number_of_children = fields.Integer('Number of Children',
|
||||
compute='_compute_number_of_children')
|
||||
|
||||
mother_id = fields.Many2one(comodel_name='zoo.animal',
|
||||
string='Mother',
|
||||
ondelete='set null') # ondelete: 'set null', 'restrict', 'cascade'
|
||||
|
||||
mother_name = fields.Char('Mother Name',
|
||||
related='mother_id.name')
|
||||
|
||||
female_children_ids = fields.One2many(comodel_name='zoo.animal',
|
||||
inverse_name='mother_id',
|
||||
string='Female Children')
|
||||
|
||||
father_id = fields.Many2one(comodel_name='zoo.animal',
|
||||
string='Father',
|
||||
ondelete='set null')
|
||||
|
||||
father_name = fields.Char('Father Name',
|
||||
related='father_id.name')
|
||||
|
||||
male_children_ids = fields.One2many(comodel_name='zoo.animal',
|
||||
inverse_name='father_id',
|
||||
string='Male Children')
|
||||
|
||||
|
||||
toy_ids = fields.Many2many(comodel_name='product.product',
|
||||
string="Toys",
|
||||
relation='animal_product_toy_rel',
|
||||
column1='col_animal_id',
|
||||
column2='col_product_id')
|
||||
toy_ids = fields.Many2many(comodel_name='product.product',
|
||||
string="Toys",
|
||||
relation='animal_product_toy_rel',
|
||||
column1='col_animal_id',
|
||||
column2='col_product_id')
|
||||
|
||||
creature_id = fields.Many2one(comodel_name='zoo.creature', string='Creature')
|
||||
creature_id = fields.Many2one(comodel_name='zoo.creature',
|
||||
string='Creature')
|
||||
|
||||
cage_id = fields.Many2one(comodel_name='zoo.cage',
|
||||
string='Cage',
|
||||
ondelete='set null')
|
||||
|
||||
# --- Các hàm tính toán (Compute Functions) ---
|
||||
@api.depends('dob')
|
||||
def _compute_age(self):
|
||||
now = datetime.datetime.now()
|
||||
@ -65,17 +110,64 @@ class ZooAnimal(models.Model):
|
||||
record.age = False
|
||||
pass
|
||||
|
||||
@api.depends('male_children_ids')
|
||||
def _compute_number_of_children(self):
|
||||
for record in self:
|
||||
record.number_of_children = len(record.male_children_ids)
|
||||
|
||||
# --- Các hàm ràng buộc (Constraints) ---
|
||||
@api.constrains('dob')
|
||||
def _check_dob(self):
|
||||
for record in self:
|
||||
if record.dob and record.dob.year < 1900:
|
||||
raise ValidationError(_("Invalid DOB!"))
|
||||
|
||||
@api.depends('male_children_ids')
|
||||
def _compute_number_of_children(self):
|
||||
for record in self:
|
||||
record.number_of_children = len(record.male_children_ids)
|
||||
|
||||
|
||||
@api.constrains('father_id', 'mother_id')
|
||||
def _check_parents(self):
|
||||
"""Validate parent relationships"""
|
||||
for record in self:
|
||||
# Kiểm tra cha != mẹ
|
||||
if record.father_id and record.mother_id:
|
||||
if record.father_id == record.mother_id:
|
||||
raise ValidationError(_("Father and Mother cannot be the same animal!"))
|
||||
|
||||
# Kiểm tra cha != record hiện tại
|
||||
if record.father_id:
|
||||
if record.father_id.id == record.id:
|
||||
raise ValidationError(_("An animal cannot be its own father!"))
|
||||
|
||||
# Kiểm tra mẹ != record hiện tại
|
||||
if record.mother_id:
|
||||
if record.mother_id.id == record.id:
|
||||
raise ValidationError(_("An animal cannot be its own mother!"))
|
||||
|
||||
@api.constrains('gender', 'female_children_ids', 'male_children_ids')
|
||||
def _check_gender_children_consistency(self):
|
||||
"""Validate gender and children lists consistency"""
|
||||
for record in self:
|
||||
# Kiểm tra không đồng thời có cả female_children_ids và male_children_ids
|
||||
if record.female_children_ids and record.male_children_ids:
|
||||
raise ValidationError(_(
|
||||
"An animal cannot have both female children list and male children list. "
|
||||
"Please check the parent assignments."
|
||||
))
|
||||
|
||||
# Giới tính đực (male) không được phép có female_children_ids
|
||||
if record.gender == 'male' and record.female_children_ids:
|
||||
raise ValidationError(_(
|
||||
"A male animal cannot have female children in the female_children_ids field. "
|
||||
"Male animals should only have children in male_children_ids (as father)."
|
||||
))
|
||||
|
||||
# Giới tính cái (female) không được phép có male_children_ids
|
||||
if record.gender == 'female' and record.male_children_ids:
|
||||
raise ValidationError(_(
|
||||
"A female animal cannot have male children in the male_children_ids field. "
|
||||
"Female animals should only have children in female_children_ids (as mother)."
|
||||
))
|
||||
|
||||
# --- Các hàm thay đổi (Onchange Functions) ---
|
||||
@api.onchange('weight')
|
||||
def _update_weight_pound(self):
|
||||
self.weight_pound = self.weight * 2.204623
|
||||
|
||||
175
addons/zoo/models/zoo_animal_meal.py
Normal file
175
addons/zoo/models/zoo_animal_meal.py
Normal file
@ -0,0 +1,175 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo import models, fields, api, _
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from odoo.tools import html2plaintext, format_date
|
||||
|
||||
class ZooAnimalMeal(models.Model):
|
||||
_name = "zoo.animal.meal"
|
||||
_description = "Batch Feeding Record"
|
||||
_inherit = ['mail.thread']
|
||||
|
||||
# Định nghĩa các field
|
||||
record_name = fields.Char(
|
||||
string='Batch Name',
|
||||
compute='_compute_record_name',
|
||||
store=True,
|
||||
readonly=True
|
||||
)
|
||||
|
||||
creature_id = fields.Many2one(
|
||||
comodel_name="zoo.creature",
|
||||
string="Species",
|
||||
required=True,
|
||||
help='Loài vật'
|
||||
)
|
||||
|
||||
meal_date = fields.Datetime(
|
||||
string="Meal Date",
|
||||
required=True,
|
||||
default=fields.Datetime.now,
|
||||
help='Thời điểm cho ăn'
|
||||
)
|
||||
|
||||
# domain logic: Chỉ chọn được Animal thuộc creature_id đã chọn
|
||||
animal_ids = fields.Many2many(
|
||||
comodel_name='zoo.animal',
|
||||
relation='zoo_animal_meal_animal_rel',
|
||||
column1='meal_id',
|
||||
column2='animal_id',
|
||||
string='Animals Fed',
|
||||
domain="[('creature_id', '=', creature_id), ('is_alive', '=', True)]"
|
||||
)
|
||||
|
||||
allowed_product_ids = fields.Many2many(comodel_name='product.product',
|
||||
string='Allowed Products',
|
||||
related='creature_id.allowed_product_ids')
|
||||
|
||||
# Chọn product để cho ăn
|
||||
product_id = fields.Many2one(comodel_name='product.product',
|
||||
string='Food Item',
|
||||
required=True,
|
||||
domain="[('id', 'in', allowed_product_ids)]")
|
||||
|
||||
uom_id = fields.Many2one(comodel_name='uom.uom',
|
||||
related='product_id.uom_id',
|
||||
string='Unit',
|
||||
readonly=True,
|
||||
required=True)
|
||||
|
||||
qty_per_animal = fields.Float(
|
||||
string='Qty per Animal',
|
||||
default=1.0,
|
||||
required=True,
|
||||
help='Số lượng thức ăn cho mỗi con vật'
|
||||
)
|
||||
|
||||
total_qty = fields.Float(
|
||||
string='Total Qty',
|
||||
compute='_compute_total_qty',
|
||||
store=True,
|
||||
help='Tổng số lượng = Qty per animal × Số động vật'
|
||||
)
|
||||
|
||||
staff_id = fields.Many2one(
|
||||
comodel_name="res.users",
|
||||
string="Staff",
|
||||
default=lambda self: self.env.user,
|
||||
required=True,
|
||||
help='Nhân viên phụ trách cho ăn'
|
||||
)
|
||||
|
||||
state = fields.Selection(
|
||||
[('draft', 'Draft'), ('done', 'Done')],
|
||||
string='State',
|
||||
default='draft',
|
||||
tracking=True
|
||||
)
|
||||
|
||||
meal_note = fields.Html(
|
||||
string='Meal Note',
|
||||
required=False,
|
||||
help='Ghi chú về bữa ăn',
|
||||
sanitize=True,
|
||||
strip_style=False,
|
||||
translate=False
|
||||
)
|
||||
|
||||
# --- Validation ---
|
||||
@api.constrains('meal_note')
|
||||
def _check_meal_note_content(self):
|
||||
for record in self:
|
||||
if record.meal_note:
|
||||
text_content = html2plaintext(record.meal_note).strip()
|
||||
if not text_content:
|
||||
raise ValidationError(_("Vui lòng nhập nội dung chi tiết về bữa ăn."))
|
||||
|
||||
# --- Computed Fields ---
|
||||
@api.depends('creature_id', 'meal_date', 'product_id')
|
||||
def _compute_record_name(self):
|
||||
for record in self:
|
||||
if record.creature_id and record.meal_date:
|
||||
date_str = format_date(self.env, record.meal_date)
|
||||
# Lấy tên món ăn
|
||||
food_name = record.product_id.name if record.product_id else "..."
|
||||
|
||||
# Format: Lion - Beef - 22/11/2025
|
||||
record.record_name = f"{record.creature_id.name} - {food_name} - {date_str}"
|
||||
else:
|
||||
record.record_name = 'New Meal'
|
||||
|
||||
@api.depends('qty_per_animal', 'animal_ids')
|
||||
def _compute_total_qty(self):
|
||||
for record in self:
|
||||
# Tổng = Định lượng * Số con
|
||||
record.total_qty = record.qty_per_animal * len(record.animal_ids)
|
||||
|
||||
# --- Actions ---
|
||||
def action_load_all_animals(self):
|
||||
"""Load all animals of selected creature"""
|
||||
for record in self:
|
||||
if not record.creature_id:
|
||||
raise UserError(_("Please select a creature first!"))
|
||||
|
||||
# Tìm tất cả thú thuộc loài này
|
||||
animals = self.env['zoo.animal'].search([
|
||||
('creature_id', '=', record.creature_id.id),
|
||||
('is_alive', '=', True)
|
||||
])
|
||||
|
||||
if not animals:
|
||||
raise UserError(_("No alive animals found for this creature!"))
|
||||
|
||||
# Gán vào field Many2many (cú pháp replace: [(6, 0, ids)])
|
||||
record.animal_ids = [(6, 0, animals.ids)]
|
||||
|
||||
return True
|
||||
|
||||
def action_mark_done(self):
|
||||
"""Mark feeding as done"""
|
||||
for record in self:
|
||||
if not record.animal_ids:
|
||||
raise UserError(_("Please select at least one animal!"))
|
||||
if not record.product_id:
|
||||
raise UserError(_("Please select a product!"))
|
||||
|
||||
record.state = 'done'
|
||||
|
||||
return True
|
||||
|
||||
def action_reset_to_draft(self):
|
||||
"""Reset to draft"""
|
||||
self.write({'state': 'draft'})
|
||||
return True
|
||||
|
||||
# Định nghĩa các hàm Action:
|
||||
def action_done(self):
|
||||
for record in self:
|
||||
# 1. Logic trừ kho (Inventory) sẽ viết ở đây
|
||||
# ...
|
||||
|
||||
# 2. Chuyển trạng thái
|
||||
record.state = 'done'
|
||||
|
||||
def action_draft(self):
|
||||
for record in self:
|
||||
record.state = 'draft'
|
||||
@ -6,15 +6,51 @@ class ZooCage(models.Model):
|
||||
_description = 'Zoo Cage'
|
||||
_order = 'name'
|
||||
|
||||
name = fields.Char(string='Cage Name', required=True)
|
||||
capacity = fields.Integer(string='Capacity', help='Maximum number of animals')
|
||||
name = fields.Char(string='Cage Name',
|
||||
required=True,
|
||||
help='Name of the cage')
|
||||
|
||||
capacity = fields.Integer(string='Capacity',
|
||||
help='Maximum number of animals')
|
||||
|
||||
location = fields.Char(string='Location')
|
||||
|
||||
cage_type = fields.Selection([
|
||||
('indoor', 'Indoor'),
|
||||
('outdoor', 'Outdoor'),
|
||||
('aquarium', 'Aquarium'),
|
||||
('aviary', 'Aviary'),
|
||||
], string='Cage Type', default='outdoor')
|
||||
area = fields.Float(string='Area (m²)', help='Cage area in square meters')
|
||||
description = fields.Text(string='Description')
|
||||
active = fields.Boolean(string='Active', default=True)
|
||||
],
|
||||
string='Cage Type',
|
||||
default='outdoor',
|
||||
required=True)
|
||||
|
||||
area = fields.Float(string='Area (m²)',
|
||||
help='Cage area in square meters')
|
||||
|
||||
description = fields.Text(string='Description',
|
||||
help='Description of the cage')
|
||||
|
||||
active = fields.Boolean(string='Active',
|
||||
default=True)
|
||||
|
||||
# Định nghĩa một Standard checklist
|
||||
checklist_template_ids = fields.One2many(
|
||||
'zoo.cage.checklist.template',
|
||||
'cage_id',
|
||||
string='Standard Checklist'
|
||||
)
|
||||
|
||||
# Định nghĩa một Standard checklist
|
||||
class ZooCageChecklistTemplate(models.Model):
|
||||
_name = 'zoo.cage.checklist.template'
|
||||
_description = 'Cage Standard Task Template'
|
||||
|
||||
cage_id = fields.Many2one(comodel_name='zoo.cage',
|
||||
string='Cage')
|
||||
|
||||
name = fields.Char(string='Task Description',
|
||||
required=True)
|
||||
|
||||
required = fields.Boolean(string='Required',
|
||||
default=True)
|
||||
|
||||
@ -6,7 +6,9 @@ class ZooCreature(models.Model):
|
||||
_name = "zoo.creature"
|
||||
_description = "Creature"
|
||||
|
||||
name = fields.Char('Name', required=True)
|
||||
name = fields.Char(string='Name',
|
||||
required=True)
|
||||
|
||||
environment = fields.Selection([
|
||||
('water', 'Water'),
|
||||
('ground', 'Ground'),
|
||||
@ -20,7 +22,32 @@ class ZooCreature(models.Model):
|
||||
('pond', 'Pond'),
|
||||
('sea', 'Sea'),
|
||||
('cool', 'Cool'),
|
||||
], string='Environment', default='ground')
|
||||
is_rare = fields.Boolean('Is Rare', default=False)
|
||||
|
||||
animal_ids = fields.One2many(comodel_name='zoo.animal', inverse_name='creature_id', string='Animals')
|
||||
],
|
||||
string='Environment',
|
||||
default='ground')
|
||||
|
||||
is_rare = fields.Boolean('Is Rare',
|
||||
default=False)
|
||||
|
||||
animal_ids = fields.One2many(comodel_name='zoo.animal',
|
||||
inverse_name='creature_id',
|
||||
string='Animals')
|
||||
|
||||
animal_count = fields.Integer(string='Animal Count',
|
||||
compute='_compute_animal_count',
|
||||
store=True)
|
||||
|
||||
allowed_product_ids = fields.Many2many(
|
||||
comodel_name='product.product',
|
||||
relation='zoo_creature_product_rel',
|
||||
column1='creature_id',
|
||||
column2='product_id',
|
||||
string='Allowed Food Products',
|
||||
help='List of food products that can be fed to this species'
|
||||
)
|
||||
|
||||
# Định nghĩa các hàm tính toán
|
||||
@api.depends('animal_ids')
|
||||
def _compute_animal_count(self):
|
||||
for record in self:
|
||||
record.animal_count = len(record.animal_ids)
|
||||
@ -82,7 +82,7 @@ class ZooHealthRecord(models.Model):
|
||||
)
|
||||
|
||||
related_cage_id = fields.Many2one(comodel_name='zoo.cage',
|
||||
string='Related Cage ID',
|
||||
string='Related Cage',
|
||||
required=False)
|
||||
|
||||
# --- Các hàm tính toán ---
|
||||
|
||||
175
addons/zoo/models/zoo_husbandry_task.py
Normal file
175
addons/zoo/models/zoo_husbandry_task.py
Normal file
@ -0,0 +1,175 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo import api, fields, models, tools, Command, _
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from odoo.tools import html2plaintext
|
||||
|
||||
import datetime
|
||||
|
||||
class ZooHusbandryTask(models.Model):
|
||||
_name = 'zoo.husbandry.task'
|
||||
_description = 'Daily Husbandry & Enrichment Task'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
_rec_name = 'display_name_custom'
|
||||
|
||||
name = fields.Char(string='Reference',
|
||||
required=True,
|
||||
copy=False,
|
||||
readonly=True,
|
||||
default=lambda self: _('New'))
|
||||
|
||||
# Ví dụ: "Lion Cage - 25/11/2025"
|
||||
display_name_custom = fields.Char(
|
||||
string='Subject',
|
||||
compute='_compute_display_name_custom',
|
||||
store=True
|
||||
)
|
||||
|
||||
cage_id = fields.Many2one(comodel_name='zoo.cage',
|
||||
domain="[('active', '=', True)]",
|
||||
string='Cage/Enclosure',
|
||||
required=True,
|
||||
tracking=True)
|
||||
|
||||
date = fields.Date(string='Date',
|
||||
default=fields.Date.context_today,
|
||||
required=True)
|
||||
|
||||
user_id = fields.Many2one(comodel_name='res.users',
|
||||
string='Assigned To',
|
||||
default=lambda self: self.env.user,
|
||||
required=True)
|
||||
|
||||
task_type = fields.Selection([
|
||||
('routine', 'Daily Routine'),
|
||||
('enrichment', 'Enrichment'),
|
||||
('maintenance', 'Minor Maintenance'),
|
||||
('vet_check', 'Visual Vet Check'),
|
||||
('observation', 'Observation'),
|
||||
('other', 'Other'),
|
||||
],
|
||||
string='Task Type',
|
||||
default='routine',
|
||||
required=True)
|
||||
|
||||
task_line_ids = fields.One2many(comodel_name='zoo.husbandry.task.line',
|
||||
inverse_name='task_id',
|
||||
string='Checklist',
|
||||
required=True)
|
||||
|
||||
keeper_note = fields.Html(string='Observations/Issues',
|
||||
help='Ghi chú của Keeper',
|
||||
sanitize=True,
|
||||
strip_style=False,
|
||||
translate=False)
|
||||
|
||||
state = fields.Selection([
|
||||
('draft', 'To Do'),
|
||||
('in_progress', 'In Progress'),
|
||||
('done', 'Done'),
|
||||
('cancel', 'Cancelled')
|
||||
], string='Status',
|
||||
default='draft',
|
||||
tracking=True,
|
||||
group_expand='_expand_groups')
|
||||
|
||||
# --- Logic đặt tên: Mã phiếu tự động
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
if vals.get('name', _('New')) == _('New'):
|
||||
# HUSB là mã sequence chúng ta sẽ định nghĩa trong XML data
|
||||
vals['name'] = self.env['ir.sequence'].next_by_code('zoo.husbandry.task') or _('New')
|
||||
return super(ZooHusbandryTask, self).create(vals)
|
||||
|
||||
# --- Logic đặt tên cho Display name
|
||||
@api.depends('cage_id', 'date', 'task_type')
|
||||
def _compute_display_name_custom(self):
|
||||
for record in self:
|
||||
cage_name = record.cage_id.name or 'Unknown Cage'
|
||||
date_str = record.date.strftime('%d/%m') if record.date else ''
|
||||
# Kết quả: "Lion Cage - 25/11"
|
||||
record.display_name_custom = f"{cage_name} - {date_str}"
|
||||
|
||||
# --- Kanban Grouping (Để Kanban hiển thị đủ cột Draft/Done dù không có data) ---
|
||||
@api.model
|
||||
def _expand_groups(self, states, domain, order=None):
|
||||
"""Force display all state columns in Kanban, even if empty"""
|
||||
return ['draft', 'in_progress', 'done', 'cancel']
|
||||
|
||||
# --- ACTIONS ---
|
||||
def action_start(self):
|
||||
self.state = 'in_progress'
|
||||
|
||||
def action_done(self):
|
||||
# Validation: Không cho Done nếu chưa check hết các mục bắt buộc
|
||||
unfinished_lines = self.task_line_ids.filtered(lambda l: l.required and not l.is_done)
|
||||
if unfinished_lines:
|
||||
raise ValidationError(_("You must complete all required checklist items before finishing!"))
|
||||
self.state = 'done'
|
||||
|
||||
def action_cancel(self):
|
||||
self.state = 'cancel'
|
||||
|
||||
def action_draft(self):
|
||||
self.state = 'draft'
|
||||
|
||||
# --- Validate kỹ hơn vì required=True của HTML đôi khi vẫn lọt lưới nếu chỉ nhập dấu cách
|
||||
@api.constrains('keeper_note')
|
||||
def _check_keeper_note_content(self):
|
||||
for record in self:
|
||||
if record.keeper_note:
|
||||
# Chuyển HTML sang text thuần để kiểm tra độ dài thực
|
||||
text_content = html2plaintext(record.keeper_note).strip()
|
||||
if not text_content:
|
||||
raise ValidationError("Vui lòng nhập nội dung chi tiết điều trị (không được để trống hoặc chỉ nhập khoảng trắng).")
|
||||
|
||||
# Load template task mỗi khi chọn Chuồng
|
||||
@api.onchange('cage_id')
|
||||
def _onchange_cage_id(self):
|
||||
"""
|
||||
Khi chọn Chuồng:
|
||||
1. Xóa sạch checklist cũ (nếu có).
|
||||
2. Copy checklist mẫu từ cấu hình Chuồng sang Task này.
|
||||
"""
|
||||
if not self.cage_id:
|
||||
return
|
||||
|
||||
# Bước 1: Chuẩn bị danh sách lệnh
|
||||
# Command.clear(): Xóa hết dòng cũ (tránh bị double khi user chọn lại chuồng)
|
||||
lines_commands = [Command.clear()]
|
||||
|
||||
# Bước 2: Lấy mẫu từ zoo.cage
|
||||
if self.cage_id.checklist_template_ids:
|
||||
for template in self.cage_id.checklist_template_ids:
|
||||
# Command.create(values): Tạo dòng mới trong RAM (chưa lưu DB)
|
||||
lines_commands.append(Command.create({
|
||||
'name': template.name,
|
||||
'required': template.required,
|
||||
'is_done': False, # Mặc định chưa làm
|
||||
}))
|
||||
|
||||
# Bước 3: Gán vào field One2many của Task
|
||||
self.task_line_ids = lines_commands
|
||||
|
||||
# --- CHECKLIST ---
|
||||
class ZooHusbandryTaskLine(models.Model):
|
||||
_name = 'zoo.husbandry.task.line'
|
||||
_description = 'Task Checklist'
|
||||
|
||||
|
||||
task_id = fields.Many2one(comodel_name='zoo.husbandry.task',
|
||||
string='Task',
|
||||
required=True,
|
||||
ondelete='cascade')
|
||||
|
||||
name = fields.Char(string='Description',
|
||||
required=True)
|
||||
|
||||
is_done = fields.Boolean(string='Done',
|
||||
default=False)
|
||||
|
||||
required = fields.Boolean(string='Required',
|
||||
default=True,
|
||||
help="If checked, this item must be done to finish the task.")
|
||||
|
||||
remark = fields.Char(string='Remark',
|
||||
help="Quick note issues here (e.g. Broken lock)")
|
||||
@ -4,4 +4,12 @@ access_zoo_creature,access_zoo_creature,model_zoo_creature,base.group_user,1,1,1
|
||||
access_zoo_cage,access_zoo_cage,model_zoo_cage,base.group_user,1,1,1,1
|
||||
access_zoo_health_record,access_zoo_health_record,model_zoo_health_record,base.group_user,1,1,1,1
|
||||
access_zoo_diet_plan,access_zoo_diet_plan,model_zoo_diet_plan,base.group_user,1,1,1,1
|
||||
access_zoo_diet_line,access_zoo_diet_line,model_zoo_diet_line,base.group_user,1,1,1,1
|
||||
access_zoo_diet_line,access_zoo_diet_line,model_zoo_diet_line,base.group_user,1,1,1,1
|
||||
access_zoo_toy_add_wizard,access_zoo_toy_add_wizard,model_zoo_toy_add_wizard,base.group_user,1,1,1,1
|
||||
access_zoo_cage_update_wizard,access_zoo_cage_update_wizard,model_zoo_cage_update_wizard,base.group_user,1,1,1,1
|
||||
access_zoo_animal_meal,access_zoo_animal_meal,model_zoo_animal_meal,base.group_user,1,1,1,1
|
||||
access_zoo_animal_feeding_wizard,access_zoo_animal_feeding_wizard,model_zoo_animal_feeding_wizard,base.group_user,1,1,1,1
|
||||
access_zoo_animal_feeding_wizard_line,access_zoo_animal_feeding_wizard_line,model_zoo_animal_feeding_wizard_line,base.group_user,1,1,1,1
|
||||
access_zoo_husbandry_task,access_zoo_husbandry_task,model_zoo_husbandry_task,base.group_user,1,1,1,1
|
||||
access_zoo_husbandry_task_line,access_zoo_husbandry_task_line,model_zoo_husbandry_task_line,base.group_user,1,1,1,1
|
||||
access_zoo_cage_checklist_template,access_zoo_cage_checklist_template,model_zoo_cage_checklist_template,base.group_user,1,1,1,1
|
||||
|
200
addons/zoo/views/zoo_animal_meal_views.xml
Normal file
200
addons/zoo/views/zoo_animal_meal_views.xml
Normal file
@ -0,0 +1,200 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<!-- Định nghĩa Form view -->
|
||||
<record id="zoo_animal_meal_form_view" model="ir.ui.view">
|
||||
<field name="name">zoo.animal.meal.form.view</field>
|
||||
<field name="model">zoo.animal.meal</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Feeding Batch">
|
||||
<header>
|
||||
<button name="action_load_all_animals" string="Load All Animals"
|
||||
type="object" class="btn-secondary"
|
||||
invisible="state != 'draft' or not creature_id"/>
|
||||
|
||||
<button name="action_done" string="Confirm Feed"
|
||||
type="object" class="oe_highlight"
|
||||
invisible="state != 'draft'"/>
|
||||
|
||||
<button name="action_draft" string="Set to Draft"
|
||||
type="object"
|
||||
invisible="state != 'done'"/>
|
||||
|
||||
<field name="state" widget="statusbar" statusbar_visible="draft,done"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<widget name="web_ribbon" title="Fed" bg_color="bg-success" invisible="state != 'done'"/>
|
||||
|
||||
<div class="oe_title">
|
||||
<label for="record_name" string="Batch Reference"/>
|
||||
<h1><field name="record_name" placeholder="e.g. Lion - Beef - 22/11/2025"/></h1>
|
||||
</div>
|
||||
|
||||
<group>
|
||||
<group string="Planning">
|
||||
<field name="creature_id" readonly="state == 'done'"/>
|
||||
|
||||
<field name="allowed_product_ids" invisible="1"/>
|
||||
|
||||
<field name="product_id" readonly="state == 'done'"
|
||||
options="{'no_create': True, 'no_open': True}"/>
|
||||
|
||||
<field name="meal_date" readonly="state == 'done'"/>
|
||||
</group>
|
||||
<group string="Quantities">
|
||||
<field name="qty_per_animal" readonly="state == 'done'"/>
|
||||
|
||||
<label for="total_qty"/>
|
||||
<div class="o_row">
|
||||
<field name="total_qty" class="fw-bold fs-4"/>
|
||||
<field name="uom_id"/>
|
||||
</div>
|
||||
<field name="staff_id"/>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
<notebook>
|
||||
<page string="Animals Fed">
|
||||
<field name="animal_ids" readonly="state == 'done'"
|
||||
widget="many2many"
|
||||
domain="[('creature_id', '=', creature_id), ('is_alive', '=', True)]">
|
||||
<list string="Animals" decoration-danger="is_alive == False">
|
||||
<field name="name"/>
|
||||
<field name="age"/> <field name="is_alive" column_invisible="True"/>
|
||||
</list>
|
||||
</field>
|
||||
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Định nghĩa List view -->
|
||||
<record id="zoo_animal_meal_list_view" model="ir.ui.view">
|
||||
<field name="name">zoo.animal.meal.list.view</field>
|
||||
<field name="model">zoo.animal.meal</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Feeding Batches" multi_edit="1"
|
||||
decoration-muted="state == 'done'"
|
||||
decoration-info="state == 'draft'">
|
||||
|
||||
<field name="record_name" decoration-bf="1"/>
|
||||
<field name="meal_date"/>
|
||||
<field name="creature_id"/>
|
||||
<field name="product_id"/>
|
||||
|
||||
<field name="animal_ids" widget="many2many_tags" optional="hide"/>
|
||||
|
||||
<field name="qty_per_animal" optional="show"/>
|
||||
|
||||
<field name="total_qty" sum="Total Consumed" decoration-bf="1"/>
|
||||
<field name="uom_id" nolabel="1"/>
|
||||
|
||||
<field name="staff_id"/>
|
||||
<field name="state" widget="badge"
|
||||
decoration-success="state == 'done'"
|
||||
decoration-info="state == 'draft'"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Định nghĩa Kanban View -->
|
||||
<record id="zoo_animal_meal_kanban_view" model="ir.ui.view">
|
||||
<field name="name">zoo.animal.meal.kanban.view</field>
|
||||
<field name="model">zoo.animal.meal</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban default_group_by="state" sample="1" records_draggable="false">
|
||||
<field name="id"/>
|
||||
<field name="record_name"/>
|
||||
<field name="creature_id"/>
|
||||
<field name="product_id"/>
|
||||
<field name="total_qty"/>
|
||||
<field name="uom_id"/>
|
||||
<field name="meal_date"/>
|
||||
<field name="animal_ids"/>
|
||||
<field name="state"/>
|
||||
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div class="oe_kanban_global_click oe_kanban_card d-flex flex-column">
|
||||
<div class="o_kanban_record_top mb-2">
|
||||
<div class="o_kanban_record_headings">
|
||||
<strong class="o_kanban_record_title">
|
||||
<field name="record_name"/>
|
||||
</strong>
|
||||
</div>
|
||||
<div class="o_kanban_top_right">
|
||||
<widget name="web_ribbon" title="Done" bg_color="bg-success" invisible="state != 'done'"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="o_kanban_content flex-grow-1">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<span class="text-muted small">Species</span><br/>
|
||||
<strong><field name="creature_id"/></strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2 text-muted fst-italic">
|
||||
<i class="fa fa-cutlery me-1"/> <field name="product_id"/>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<span class="text-muted small">Total Food</span><br/>
|
||||
<span class="badge text-bg-primary fs-6">
|
||||
<field name="total_qty"/> <field name="uom_id"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="o_kanban_record_bottom mt-2 pt-2 border-top">
|
||||
<div class="oe_kanban_bottom_left">
|
||||
</div>
|
||||
<div class="oe_kanban_bottom_right">
|
||||
<field name="staff_id" widget="many2one_avatar_user"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Định nghĩa Action -->
|
||||
<record id="action_zoo_animal_meal" model="ir.actions.act_window">
|
||||
<field name="name">Animal Meals</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">zoo.animal.meal</field>
|
||||
<field name="view_mode">list,form,kanban</field>
|
||||
<field name="view_ids" eval="[(5, 0, 0),
|
||||
(0, 0, {'view_mode': 'list', 'view_id': ref('zoo_animal_meal_list_view')}),
|
||||
(0, 0, {'view_mode': 'form', 'view_id': ref('zoo_animal_meal_form_view')}),
|
||||
(0, 0, {'view_mode': 'kanban', 'view_id': ref('zoo_animal_meal_kanban_view')}),
|
||||
]"/>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Create a new Batch Feeding Record
|
||||
</p>
|
||||
<p>
|
||||
Track feeding sessions for multiple animals of the same species.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Định nghĩa Menu -->
|
||||
<menuitem id="menu_zoo_animal_nutrition"
|
||||
name="Animal Nutrition"
|
||||
sequence="50"
|
||||
parent="menu_zoo"
|
||||
groups="base.group_user"/>
|
||||
|
||||
<menuitem id="menu_zoo_animal_meal"
|
||||
name="Animal Meals"
|
||||
sequence="10"
|
||||
parent="menu_zoo_animal_nutrition"
|
||||
action="action_zoo_animal_meal"
|
||||
groups="base.group_user"/>
|
||||
</data>
|
||||
</odoo>
|
||||
@ -9,7 +9,7 @@
|
||||
<form>
|
||||
<sheet>
|
||||
<div class="oe_title">
|
||||
<label for="name" string="Animal" class="oe_edit_only"/>
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1><field name="name" placeholder="e.g. Peter White Tiger"/></h1>
|
||||
<label for="is_alive"/>
|
||||
<field name="is_alive"/>
|
||||
@ -28,6 +28,7 @@
|
||||
<group>
|
||||
<field name="image" widget="image"/>
|
||||
<field name="creature_id"/>
|
||||
<field name="cage_id"/>
|
||||
<field name="gender"/>
|
||||
<field name="dob"/>
|
||||
<field name="age"/>
|
||||
@ -117,14 +118,53 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Định nghĩa Kanban view -->
|
||||
<record model="ir.ui.view" id="zoo_animal_kanban_view">
|
||||
<field name="name">zoo.animal.kanban.view</field>
|
||||
<field name="model">zoo.animal</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban class="o_kanban_mobile">
|
||||
<field name="id"/>
|
||||
<field name="name"/>
|
||||
<field name="feed_time"/>
|
||||
<field name="gender"/>
|
||||
<field name="veterinarian_id"/>
|
||||
<field name="cage_id"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div t-attf-class="oe_kanban_global_click o_kanban_record_has_image_fill">
|
||||
<div class="o_kanban_image_fill_left o_kanban_image_full" t-attf-style="background-image: url(#{kanban_image('zoo.animal', 'image', record.id.raw_value, placeholder)})" role="img"/>
|
||||
|
||||
<div class="oe_kanban_details">
|
||||
<strong class="o_kanban_record_title"><field name="name"/></strong> (<t t-esc="record.gender.value"/>)
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<i class="fa fa-home" role="img" aria-label="Cage" title="Cage"/>
|
||||
<field name="cage_id"/>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<i class="fa fa-clock-o" role="img" aria-label="Feed Time" title="Feed Time"/>
|
||||
<t t-esc="record.feed_time.value"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Định nghĩa Action -->
|
||||
<record id="action_zoo_animal" model="ir.actions.act_window">
|
||||
<field name="name">Zoo Animal</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">zoo.animal</field>
|
||||
<field name="view_mode">list,form,kanban</field>
|
||||
<field name="view_ids" eval="[(5, 0, 0),
|
||||
(0, 0, {'view_mode': 'list', 'view_id': ref('zoo_animal_list_view')}),
|
||||
(0, 0, {'view_mode': 'form', 'view_id': ref('zoo_animal_form_view')})]"/>
|
||||
(0, 0, {'view_mode': 'form', 'view_id': ref('zoo_animal_form_view')}),
|
||||
(0, 0, {'view_mode': 'kanban', 'view_id': ref('zoo_animal_kanban_view')})]"/>
|
||||
</record>
|
||||
|
||||
<!-- Định nghĩa Menu -->
|
||||
|
||||
@ -8,31 +8,104 @@
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<sheet>
|
||||
<widget name="web_ribbon" title="Archived" bg_color="bg-danger" invisible="active"/>
|
||||
|
||||
<div class="oe_title">
|
||||
<label for="name" string="Cage" class="oe_edit_only"/>
|
||||
<h1><field name="name" placeholder="e.g. Cage A1"/></h1>
|
||||
<label for="name" string="Cage Name"/>
|
||||
<h1><field name="name" placeholder="e.g. Lion Enclosure A"/></h1>
|
||||
</div>
|
||||
|
||||
<group name="basic_information">
|
||||
<group>
|
||||
<field name="cage_type"/>
|
||||
<field name="location"/>
|
||||
<field name="capacity"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="area"/>
|
||||
<field name="active"/>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
|
||||
<group>
|
||||
<field name="description" colspan="2" nolabel="1" placeholder="Description about the cage..."/>
|
||||
<group>
|
||||
<field name="location"/>
|
||||
<field name="cage_type"/>
|
||||
<field name="active" invisible="1"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="capacity"/>
|
||||
<field name="area"/>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
<notebook>
|
||||
<page string="Description">
|
||||
<field name="description" placeholder="Cage details..."/>
|
||||
</page>
|
||||
|
||||
<page string="Standard Checklist" name="checklist_template">
|
||||
<field name="checklist_template_ids">
|
||||
<list editable="bottom">
|
||||
<field name="name" placeholder="Task description (e.g. Check water)"/>
|
||||
<field name="required"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Định nghĩa Kanban View -->
|
||||
<record id="zoo_cage_kanban_view" model="ir.ui.view">
|
||||
<field name="name">zoo.cage.kanban.view</field>
|
||||
<field name="model">zoo.cage</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban default_group_by="cage_type" records_draggable="false" sample="1">
|
||||
<field name="id"/>
|
||||
<field name="name"/>
|
||||
<field name="location"/>
|
||||
<field name="capacity"/>
|
||||
<field name="area"/>
|
||||
<field name="active"/>
|
||||
<field name="cage_type"/>
|
||||
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div class="oe_kanban_global_click oe_kanban_card o_kanban_record_has_image_fill">
|
||||
<widget name="web_ribbon" title="Archived" bg_color="bg-danger" invisible="active"/>
|
||||
|
||||
<div class="oe_kanban_details d-flex flex-column">
|
||||
<strong class="o_kanban_record_title">
|
||||
<field name="name"/>
|
||||
</strong>
|
||||
|
||||
<div class="o_kanban_tags_section"/>
|
||||
<div class="text-muted">
|
||||
<i class="fa fa-map-marker me-1" title="Location"/>
|
||||
<field name="location"/>
|
||||
</div>
|
||||
<div class="mt-1">
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<i class="fa fa-arrows-alt me-1" title="Area"/>
|
||||
<span><field name="area"/> m²</span>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<i class="fa fa-building me-1" title="Cage Type"/>
|
||||
<field name="cage_type"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="o_kanban_record_bottom mt-auto">
|
||||
<div class="oe_kanban_bottom_left">
|
||||
<span class="badge rounded-pill text-bg-info">
|
||||
<i class="fa fa-users me-1"/> <field name="capacity"/>
|
||||
</span>
|
||||
</div>
|
||||
<div class="oe_kanban_bottom_right">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<!-- Định nghĩa List view -->
|
||||
<record id="zoo_cage_list_view" model="ir.ui.view">
|
||||
<field name="name">zoo.cage.list.view</field>
|
||||
@ -54,9 +127,12 @@
|
||||
<field name="name">Cage</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">zoo.cage</field>
|
||||
<field name="view_mode">list,form,kanban</field>
|
||||
<field name="view_ids" eval="[(5, 0, 0),
|
||||
(0, 0, {'view_mode': 'list', 'view_id': ref('zoo_cage_list_view')}),
|
||||
(0, 0, {'view_mode': 'form', 'view_id': ref('zoo_cage_form_view')})]"/>
|
||||
(0, 0, {'view_mode': 'form', 'view_id': ref('zoo_cage_form_view')}),
|
||||
(0, 0, {'view_mode': 'kanban', 'view_id': ref('zoo_cage_kanban_view')})
|
||||
]"/>
|
||||
</record>
|
||||
|
||||
<!-- Định nghĩa Menu -->
|
||||
|
||||
@ -22,7 +22,11 @@
|
||||
<group>
|
||||
<field name="animal_ids"/>
|
||||
</group>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
<group string="Feeding Information">
|
||||
<field name="allowed_product_ids" widget="many2many_tags"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
@ -41,14 +45,71 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Định nghĩa Kanban View -->
|
||||
<record id="zoo_creature_kanban_view" model="ir.ui.view">
|
||||
<field name="name">zoo.creature.kanban.view</field>
|
||||
<field name="model">zoo.creature</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban default_group_by="environment" records_draggable="false" sample="1">
|
||||
<field name="id"/>
|
||||
<field name="name"/>
|
||||
<field name="is_rare"/>
|
||||
<field name="environment"/>
|
||||
<field name="animal_count"/>
|
||||
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div class="oe_kanban_global_click oe_kanban_card d-flex flex-column">
|
||||
|
||||
<widget name="web_ribbon" title="Rare" bg_color="bg-danger" invisible="not is_rare"/>
|
||||
|
||||
<div class="o_kanban_content">
|
||||
<div class="o_kanban_record_title fw-bold fs-5 mb-1">
|
||||
<field name="name"/>
|
||||
</div>
|
||||
|
||||
<div class="text-muted">
|
||||
<i class="fa fa-globe me-1" title="Environment"/>
|
||||
<field name="environment"/>
|
||||
</div>
|
||||
|
||||
<div class="o_kanban_record_bottom mt-auto pt-2">
|
||||
<div class="oe_kanban_bottom_left">
|
||||
<span class="badge rounded-pill text-bg-success">
|
||||
<i class="fa fa-paw me-1"/> <field name="animal_count"/>
|
||||
</span>
|
||||
</div>
|
||||
<div class="oe_kanban_bottom_right">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<!-- Định nghĩa Action -->
|
||||
<record id="action_zoo_creature" model="ir.actions.act_window">
|
||||
<field name="name">Creature</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">zoo.creature</field>
|
||||
<field name="view_mode">kanban,list,form</field>
|
||||
<field name="view_ids" eval="[(5, 0, 0),
|
||||
(0, 0, {'view_mode': 'list', 'view_id': ref('zoo_creature_list_view')}),
|
||||
(0, 0, {'view_mode': 'form', 'view_id': ref('zoo_creature_form_view')})]"/>
|
||||
(0, 0, {'view_mode': 'form', 'view_id': ref('zoo_creature_form_view')}),
|
||||
(0, 0, {'view_mode': 'kanban', 'view_id': ref('zoo_creature_kanban_view')})
|
||||
]"/>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Create a new Creature Species
|
||||
</p>
|
||||
<p>
|
||||
Define species like Lion, Shark, or Eagle and assign their environments.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Định nghĩa Menu -->
|
||||
|
||||
@ -133,10 +133,10 @@
|
||||
|
||||
<!-- Định nghĩa Menu -->
|
||||
<menuitem id="menu_zoo_diet_plan"
|
||||
name="Diet Plans"
|
||||
name="Animal Diet Plans"
|
||||
action="action_zoo_diet_plan"
|
||||
sequence="50"
|
||||
parent="menu_zoo"
|
||||
sequence="20"
|
||||
parent="menu_zoo_animal_nutrition"
|
||||
groups="base.group_user"/>
|
||||
|
||||
</data>
|
||||
|
||||
222
addons/zoo/views/zoo_husbandry_task_views.xml
Normal file
222
addons/zoo/views/zoo_husbandry_task_views.xml
Normal file
@ -0,0 +1,222 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<!-- Định nghĩa Search view -->
|
||||
<record id="zoo_husbandry_task_search_view" model="ir.ui.view">
|
||||
<field name="name">zoo.husbandry.task.search</field>
|
||||
<field name="model">zoo.husbandry.task</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Tasks">
|
||||
<field name="name"/>
|
||||
<field name="cage_id"/>
|
||||
<field name="user_id"/>
|
||||
|
||||
<separator/>
|
||||
<filter string="My Tasks" name="my_tasks" domain="[('user_id', '=', uid)]"/>
|
||||
<!-- <filter string="Today" name="today" domain="[('date', '=', context_today().strftime('%Y-%m-%d'))]"/> -->
|
||||
<filter string="Today" name="today"
|
||||
domain="[('date', '=', context_today())]"/>
|
||||
|
||||
<separator/>
|
||||
<filter string="To Do" name="draft" domain="[('state', '=', 'draft')]"/>
|
||||
<filter string="In Progress" name="progress" domain="[('state', '=', 'in_progress')]"/>
|
||||
|
||||
<group expand="0" string="Group By">
|
||||
<filter string="Cage" name="group_cage" context="{'group_by': 'cage_id'}"/>
|
||||
<filter string="Status" name="group_state" context="{'group_by': 'state'}"/>
|
||||
<filter string="Task Type" name="group_type" context="{'group_by': 'task_type'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Định nghĩa Form view -->
|
||||
<record id="zoo_daily_task_form_view" model="ir.ui.view">
|
||||
<field name="name">zoo.husbandry.task.form</field>
|
||||
<field name="model">zoo.husbandry.task</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Husbandry Task">
|
||||
<header>
|
||||
<button name="action_start" string="Start Working" type="object" class="btn-primary"
|
||||
invisible="state != 'draft'"/>
|
||||
|
||||
<button name="action_done" string="Mark as Done" type="object" class="btn-success"
|
||||
invisible="state != 'in_progress'"/>
|
||||
|
||||
<button name="action_cancel" string="Cancel" type="object"
|
||||
invisible="state in ('done', 'cancel')"/>
|
||||
|
||||
<button name="action_draft" string="Reset to Draft" type="object"
|
||||
invisible="state != 'cancel'"/>
|
||||
|
||||
<field name="state" widget="statusbar" statusbar_visible="draft,in_progress,done"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<widget name="web_ribbon" title="Done" bg_color="bg-success" invisible="state != 'done'"/>
|
||||
|
||||
<div class="oe_title">
|
||||
<h1><field name="display_name_custom"/></h1>
|
||||
<h3 class="text-muted"><field name="name"/></h3>
|
||||
</div>
|
||||
|
||||
<group>
|
||||
<group>
|
||||
<field name="cage_id" readonly="state != 'draft'"/>
|
||||
<field name="date" readonly="state == 'done'"/>
|
||||
<field name="task_type" widget="radio" options="{'horizontal': true}" readonly="state == 'done'"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="user_id" widget="many2one_avatar_user" readonly="state == 'done'"/>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
<notebook>
|
||||
<page string="Checklist" name="checklist">
|
||||
<field name="task_line_ids" readonly="state == 'done'">
|
||||
<list editable="bottom" decoration-success="is_done">
|
||||
<field name="is_done" widget="boolean_toggle" string="Done?"/>
|
||||
<field name="required" column_invisible="True"/>
|
||||
|
||||
<field name="name" decoration-bf="required"/>
|
||||
|
||||
<field name="remark" placeholder="Note issues..."/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
|
||||
<page string="Notes & Issues" name="notes">
|
||||
<field name="keeper_note" placeholder="Describe any health issues, maintenance needs, or behavioral observations..."/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
<!-- Cú pháp mới để load thẻ chatter -->
|
||||
<chatter reload_on_post="True"/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Định nghĩa List view -->
|
||||
<record id="zoo_daily_task_list_view" model="ir.ui.view">
|
||||
<field name="name">zoo.husbandry.task.list</field>
|
||||
<field name="model">zoo.husbandry.task</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Daily Tasks"
|
||||
decoration-muted="state in ('done', 'cancel')"
|
||||
decoration-info="state == 'draft'"
|
||||
decoration-warning="state == 'in_progress'">
|
||||
|
||||
<field name="name" optional="hide"/>
|
||||
<field name="display_name_custom" string="Subject"/>
|
||||
<field name="cage_id"/>
|
||||
<field name="task_type" widget="badge" decoration-info="task_type == 'routine'"/>
|
||||
<field name="date"/>
|
||||
<field name="user_id" widget="many2one_avatar_user"/>
|
||||
<field name="state" widget="badge"
|
||||
decoration-success="state == 'done'"
|
||||
decoration-info="state == 'draft'"
|
||||
decoration-warning="state == 'in_progress'"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Định nghĩa Kanban view -->
|
||||
<record id="zoo_daily_task_kanban_view" model="ir.ui.view">
|
||||
<field name="name">zoo.husbandry.task.kanban</field>
|
||||
<field name="model">zoo.husbandry.task</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban default_group_by="state" sample="1" class="o_kanban_small_column">
|
||||
<field name="id"/>
|
||||
<field name="display_name_custom"/>
|
||||
<field name="name"/>
|
||||
<field name="cage_id"/>
|
||||
<field name="task_type"/>
|
||||
<field name="state"/>
|
||||
<field name="user_id"/>
|
||||
<field name="date"/>
|
||||
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div class="oe_kanban_global_click">
|
||||
<div class="o_kanban_record_top mb-2">
|
||||
<div class="o_kanban_record_headings">
|
||||
<strong class="o_kanban_record_title">
|
||||
<field name="display_name_custom"/>
|
||||
</strong>
|
||||
<small class="text-muted d-block mt-1">
|
||||
<i class="fa fa-map-marker me-1"/> <field name="cage_id"/>
|
||||
</small>
|
||||
</div>
|
||||
<div class="o_kanban_record_top_right">
|
||||
<field name="state" widget="label_selection"
|
||||
options="{'classes': {'draft': 'default', 'in_progress': 'warning', 'done': 'success', 'cancel': 'danger'}}"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="o_kanban_record_body">
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<span class="badge rounded-pill text-bg-info">
|
||||
<field name="task_type"/>
|
||||
</span>
|
||||
<span class="text-muted small">
|
||||
<field name="date" widget="date"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="o_kanban_record_bottom mt-2 pt-2 border-top">
|
||||
<div class="oe_kanban_bottom_left text-muted">
|
||||
<small><field name="name"/></small>
|
||||
</div>
|
||||
<div class="oe_kanban_bottom_right">
|
||||
<field name="user_id" widget="many2one_avatar_user"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Định nghĩa Action -->
|
||||
<record id="action_zoo_daily_tasks" model="ir.actions.act_window">
|
||||
<field name="name">Keeper Daily Tasks</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">zoo.husbandry.task</field>
|
||||
<field name="view_mode">kanban,list,form</field>
|
||||
<field name="context">{'search_default_my_tasks': 1}</field>
|
||||
<field name="search_view_id" ref="zoo_husbandry_task_search_view"/>
|
||||
<field name="view_ids" eval="[(5, 0, 0),
|
||||
(0, 0, {'view_mode': 'kanban', 'view_id': ref('zoo_daily_task_kanban_view')}),
|
||||
(0, 0, {'view_mode': 'list', 'view_id': ref('zoo_daily_task_list_view')}),
|
||||
(0, 0, {'view_mode': 'form', 'view_id': ref('zoo_daily_task_form_view')})]"/>
|
||||
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
No tasks found for today!
|
||||
</p>
|
||||
<p>
|
||||
Tasks are usually generated automatically based on the Cage schedule.
|
||||
You can also create an ad-hoc task manually.
|
||||
</p>
|
||||
</field>
|
||||
|
||||
</record>
|
||||
|
||||
<!-- Định nghĩa Menu -->
|
||||
<menuitem id="menu_zoo_husbandry_and_care"
|
||||
name="Husbandry and Care"
|
||||
sequence="60"
|
||||
parent="menu_zoo"
|
||||
groups="base.group_user"/>
|
||||
|
||||
<menuitem id="menu_zoo_daily_tasks"
|
||||
name="Keeper Daily Tasks"
|
||||
sequence="10"
|
||||
parent="menu_zoo_husbandry_and_care"
|
||||
action="action_zoo_daily_tasks"
|
||||
groups="base.group_user"/>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
3
addons/zoo/wizard/__init__.py
Normal file
3
addons/zoo/wizard/__init__.py
Normal file
@ -0,0 +1,3 @@
|
||||
from . import toy_add
|
||||
from . import cage_update
|
||||
from . import animal_feeding
|
||||
BIN
addons/zoo/wizard/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
addons/zoo/wizard/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
addons/zoo/wizard/__pycache__/animal_feeding.cpython-312.pyc
Normal file
BIN
addons/zoo/wizard/__pycache__/animal_feeding.cpython-312.pyc
Normal file
Binary file not shown.
BIN
addons/zoo/wizard/__pycache__/cage_update.cpython-312.pyc
Normal file
BIN
addons/zoo/wizard/__pycache__/cage_update.cpython-312.pyc
Normal file
Binary file not shown.
BIN
addons/zoo/wizard/__pycache__/toy_add.cpython-312.pyc
Normal file
BIN
addons/zoo/wizard/__pycache__/toy_add.cpython-312.pyc
Normal file
Binary file not shown.
131
addons/zoo/wizard/animal_feeding.py
Normal file
131
addons/zoo/wizard/animal_feeding.py
Normal file
@ -0,0 +1,131 @@
|
||||
from odoo import models, fields, api, Command, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
class AnimalFeedingWizard(models.TransientModel):
|
||||
_name = 'zoo.animal.feeding.wizard'
|
||||
_description = 'Bulk Feeding Wizard'
|
||||
|
||||
def _default_animals(self):
|
||||
"""Lấy danh sách thú được chọn từ màn hình List View"""
|
||||
return self.env.context.get('active_ids', [])
|
||||
|
||||
# 1. Context Fields
|
||||
animal_ids = fields.Many2many(
|
||||
comodel_name='zoo.animal',
|
||||
string='Animals to Feed',
|
||||
required=True,
|
||||
default=_default_animals
|
||||
)
|
||||
|
||||
# Tự động tính loài (dùng để validate và lọc thức ăn)
|
||||
creature_id = fields.Many2one(
|
||||
comodel_name='zoo.creature',
|
||||
string='Species',
|
||||
compute='_compute_creature_info',
|
||||
store=True,
|
||||
readonly=True
|
||||
)
|
||||
|
||||
meal_date = fields.Datetime(
|
||||
string="Feeding Time",
|
||||
required=True,
|
||||
default=fields.Datetime.now
|
||||
)
|
||||
|
||||
staff_id = fields.Many2one(
|
||||
comodel_name="res.users",
|
||||
string="Staff",
|
||||
default=lambda self: self.env.user,
|
||||
required=True
|
||||
)
|
||||
|
||||
allowed_product_ids = fields.Many2many(
|
||||
related='creature_id.allowed_product_ids',
|
||||
readonly=True
|
||||
)
|
||||
|
||||
# 2. Feeding Lines (Bảng tạm để chọn nhiều món)
|
||||
line_ids = fields.One2many(
|
||||
comodel_name='zoo.animal.feeding.wizard.line',
|
||||
inverse_name='wizard_id',
|
||||
string='Food Lines'
|
||||
)
|
||||
|
||||
# --- Compute & Validation ---
|
||||
@api.depends('animal_ids')
|
||||
def _compute_creature_info(self):
|
||||
for record in self:
|
||||
if not record.animal_ids:
|
||||
record.creature_id = False
|
||||
continue
|
||||
|
||||
# Lấy loài của con đầu tiên
|
||||
first_creature = record.animal_ids[0].creature_id
|
||||
|
||||
# Validate: Tất cả thú được chọn phải cùng 1 loài
|
||||
# Nếu có con nào khác loài với con đầu tiên -> Báo lỗi hoặc False
|
||||
if any(animal.creature_id != first_creature for animal in record.animal_ids):
|
||||
raise UserError(_("You can only feed animals of the SAME species in a single batch!"))
|
||||
|
||||
record.creature_id = first_creature
|
||||
|
||||
# --- Main Action ---
|
||||
def action_confirm(self):
|
||||
self.ensure_one()
|
||||
|
||||
if not self.line_ids:
|
||||
raise UserError(_("Please select at least one food item to feed."))
|
||||
|
||||
MealObj = self.env['zoo.animal.meal']
|
||||
created_meals = []
|
||||
|
||||
# LOOP: Với mỗi dòng thức ăn trong Wizard -> Tạo 1 phiếu Batch Meal thật
|
||||
for line in self.line_ids:
|
||||
vals = {
|
||||
'creature_id': self.creature_id.id,
|
||||
'meal_date': self.meal_date,
|
||||
'staff_id': self.staff_id.id,
|
||||
'product_id': line.product_id.id,
|
||||
'qty_per_animal': line.qty_per_animal,
|
||||
'state': 'draft', # Mặc định là Draft để Staff check lại lần cuối
|
||||
# Link toàn bộ thú đang chọn vào phiếu
|
||||
'animal_ids': [Command.set(self.animal_ids.ids)],
|
||||
}
|
||||
new_meal = MealObj.create(vals)
|
||||
created_meals.append(new_meal.id)
|
||||
|
||||
# Return Action: Mở danh sách các phiếu vừa tạo
|
||||
return {
|
||||
'name': _('Created Feeding Batches'),
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'zoo.animal.meal',
|
||||
'view_mode': 'list,form',
|
||||
'domain': [('id', 'in', created_meals)],
|
||||
'context': {'create': False}, # Tùy chọn: Không cho tạo thêm ở màn hình kết quả
|
||||
}
|
||||
|
||||
class AnimalFeedingWizardLine(models.TransientModel):
|
||||
_name = 'zoo.animal.feeding.wizard.line'
|
||||
_description = 'Feeding Line Detail'
|
||||
|
||||
wizard_id = fields.Many2one('zoo.animal.feeding.wizard',
|
||||
required=True)
|
||||
|
||||
# Fields kỹ thuật để lấy domain từ cha
|
||||
creature_id = fields.Many2one(related='wizard_id.creature_id')
|
||||
allowed_product_ids = fields.Many2many(related='creature_id.allowed_product_ids')
|
||||
|
||||
product_id = fields.Many2one(
|
||||
'product.product',
|
||||
string='Food Item',
|
||||
required=True
|
||||
)
|
||||
|
||||
uom_id = fields.Many2one(related='product_id.uom_id',
|
||||
readonly=True)
|
||||
|
||||
qty_per_animal = fields.Float(
|
||||
string='Qty / Animal',
|
||||
default=1.0,
|
||||
required=True
|
||||
)
|
||||
55
addons/zoo/wizard/animal_feeding_views.xml
Normal file
55
addons/zoo/wizard/animal_feeding_views.xml
Normal file
@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="view_zoo_animal_feeding_wizard_form" model="ir.ui.view">
|
||||
<field name="name">zoo.animal.feeding.wizard.form</field>
|
||||
<field name="model">zoo.animal.feeding.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Bulk Feeding">
|
||||
<sheet>
|
||||
<div class="alert alert-info text-center" role="alert" invisible="not creature_id">
|
||||
Feeding <field name="creature_id" readonly="1" class="fw-bold"/>
|
||||
</div>
|
||||
|
||||
<group>
|
||||
<group>
|
||||
<field name="meal_date"/>
|
||||
<field name="staff_id" readonly="1"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="animal_ids" widget="many2many_tags_avatar" readonly="1"/>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
<separator string="Menu Selection"/>
|
||||
|
||||
<field name="allowed_product_ids" invisible="1"/>
|
||||
|
||||
<field name="line_ids">
|
||||
<list editable="bottom">
|
||||
<field name="product_id"
|
||||
domain="[('id', 'in', parent.allowed_product_ids)]"
|
||||
options="{'no_create': True, 'no_open': True}"/>
|
||||
|
||||
<field name="qty_per_animal"/>
|
||||
<field name="uom_id"/>
|
||||
</list>
|
||||
</field>
|
||||
</sheet>
|
||||
<footer>
|
||||
<button name="action_confirm" string="Confirm & Create Batches"
|
||||
type="object" class="btn-primary" data-hotkey="q"/>
|
||||
<button string="Cancel" class="btn-secondary" special="cancel" data-hotkey="z"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_zoo_animal_feeding_wizard" model="ir.actions.act_window">
|
||||
<field name="name">Feed Animals</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">zoo.animal.feeding.wizard</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field> <field name="binding_model_id" ref="model_zoo_animal"/> <field name="binding_view_types">list</field> </record>
|
||||
</data>
|
||||
</odoo>
|
||||
34
addons/zoo/wizard/cage_update.py
Normal file
34
addons/zoo/wizard/cage_update.py
Normal file
@ -0,0 +1,34 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo import api, fields, models, tools, _
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
|
||||
import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class CageUpdateWizard(models.TransientModel):
|
||||
_name = "zoo.cage.update.wizard"
|
||||
_description = "Update Cage for Animals"
|
||||
|
||||
cage_id = fields.Many2one('zoo.cage', string='Cage', required=True, help='Select cage to assign to animals')
|
||||
|
||||
def update_cage(self):
|
||||
"""Update cage for selected animals"""
|
||||
# Get selected animal IDs from context
|
||||
animal_ids = self.env.context.get('active_ids', [])
|
||||
|
||||
if not animal_ids:
|
||||
raise UserError(_("No animals selected!"))
|
||||
|
||||
# Browse and update animals
|
||||
zoo_animals = self.env["zoo.animal"].browse(animal_ids)
|
||||
|
||||
# Update cage_id for all selected animals
|
||||
zoo_animals.write({
|
||||
'cage_id': self.cage_id.id
|
||||
})
|
||||
|
||||
# Log action
|
||||
_logger.info(f"Updated cage to '{self.cage_id.name}' for {len(zoo_animals)} animal(s)")
|
||||
|
||||
# Return action to refresh the view
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
36
addons/zoo/wizard/cage_update_views.xml
Normal file
36
addons/zoo/wizard/cage_update_views.xml
Normal file
@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- Form View for Cage Update Wizard -->
|
||||
<record id="view_zoo_animal_update_cage" model="ir.ui.view">
|
||||
<field name="name">view.zoo.animal.update.cage</field>
|
||||
<field name="model">zoo.cage.update.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Update Cage">
|
||||
<group>
|
||||
<group>
|
||||
<field name="cage_id" options="{'no_create': True}"/>
|
||||
</group>
|
||||
<group/>
|
||||
</group>
|
||||
<p class="text-muted">
|
||||
This will update the cage for all selected animal(s).
|
||||
</p>
|
||||
<footer>
|
||||
<button string="Update Cage" name="update_cage" type="object" default_focus="1" class="btn-primary"/>
|
||||
<button string="Cancel" class="btn-default" special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Action for Cage Update Wizard -->
|
||||
<record id="action_zoo_animal_update_cage" model="ir.actions.act_window">
|
||||
<field name="name">Update Cage</field>
|
||||
<field name="res_model">zoo.cage.update.wizard</field>
|
||||
<field name="binding_model_id" ref="model_zoo_animal"/>
|
||||
<field name="binding_view_types">list</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
<field name="view_id" ref="view_zoo_animal_update_cage"/>
|
||||
</record>
|
||||
</odoo>
|
||||
21
addons/zoo/wizard/toy_add.py
Normal file
21
addons/zoo/wizard/toy_add.py
Normal file
@ -0,0 +1,21 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo import api, fields, models, tools, _
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
|
||||
import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class ToyAddWizard(models.TransientModel):
|
||||
_name = "zoo.toy.add.wizard"
|
||||
_description = "Add toy"
|
||||
|
||||
product_id = fields.Many2one('product.product', string='Toy', required=True)
|
||||
|
||||
def add_toy(self):
|
||||
ids = self.env.context['active_ids'] # selected record ids
|
||||
zoo_animals = self.env["zoo.animal"].browse(ids)
|
||||
# https://www.odoo.com/documentation/14.0/reference/orm.html#create-update
|
||||
data = {
|
||||
"toy_ids": [(4, self.product_id.id, 0)]
|
||||
}
|
||||
zoo_animals.write(data)
|
||||
33
addons/zoo/wizard/toy_add_views.xml
Normal file
33
addons/zoo/wizard/toy_add_views.xml
Normal file
@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_zoo_animal_add_toy" model="ir.ui.view">
|
||||
<field name="name">view.zoo.animal.add.toy</field>
|
||||
<field name="model">zoo.toy.add.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Add Toy">
|
||||
<group>
|
||||
<group>
|
||||
<field name="product_id"/>
|
||||
</group>
|
||||
<group/>
|
||||
</group>
|
||||
<p>
|
||||
Add toy to the selected record(s)?
|
||||
</p>
|
||||
<footer>
|
||||
<button string="Confirm" name="add_toy" type="object" default_focus="1" class="btn-primary"/>
|
||||
<button string="Cancel" class="btn-default" special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_zoo_animal_add_toy" model="ir.actions.act_window">
|
||||
<field name="name">Add Toy</field>
|
||||
<field name="res_model">zoo.toy.add.wizard</field>
|
||||
<field name="binding_model_id" ref="model_zoo_animal"/>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
<field name="view_id" ref="view_zoo_animal_add_toy"/>
|
||||
</record>
|
||||
</odoo>
|
||||
1
addons/zoo_plus/__init__.py
Normal file
1
addons/zoo_plus/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from . import models
|
||||
21
addons/zoo_plus/__manifest__.py
Normal file
21
addons/zoo_plus/__manifest__.py
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
'name': 'Zoo Plus',
|
||||
'summary': """Zoo Plus""",
|
||||
'description': """Building a zoo plus - Inheritence from Zoo City""",
|
||||
'author': 'Truong Phan',
|
||||
'maintainer': 'Truong Phan',
|
||||
'category': 'Uncategorized',
|
||||
'version': '0.1',
|
||||
'depends': [
|
||||
'zoo', # <-- depends on zoo (parent - Folder name) addon
|
||||
],
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
],
|
||||
'demo': [],
|
||||
'css': [],
|
||||
# 'qweb': ['static/src/xml/*.xml'],
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
'application': True,
|
||||
}
|
||||
BIN
addons/zoo_plus/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
addons/zoo_plus/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
2
addons/zoo_plus/models/__init__.py
Normal file
2
addons/zoo_plus/models/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
from . import zoo_animal
|
||||
from . import zoo_animal_backup
|
||||
BIN
addons/zoo_plus/models/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
addons/zoo_plus/models/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
addons/zoo_plus/models/__pycache__/zoo_animal.cpython-312.pyc
Normal file
BIN
addons/zoo_plus/models/__pycache__/zoo_animal.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
63
addons/zoo_plus/models/zoo_animal.py
Normal file
63
addons/zoo_plus/models/zoo_animal.py
Normal file
@ -0,0 +1,63 @@
|
||||
from odoo import api, fields, models, tools, _
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
|
||||
import requests
|
||||
|
||||
import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class ZooAnimalPlus(models.Model):
|
||||
_name = "zoo.animal" # Chỉ đúng tên của model cha
|
||||
_inherit = "zoo.animal" # Chỉ đúng tên của model cha
|
||||
_description = "Extends zoo animal model from Zoo City"
|
||||
|
||||
# modify existing fields
|
||||
name = fields.Char(string='Animal Name (+)')
|
||||
|
||||
gender = fields.Selection(selection_add=[('other', 'Other')],
|
||||
default='female',
|
||||
ondelete={'other': lambda recs: recs.write({'gender': 'male'})})
|
||||
|
||||
# add new field
|
||||
is_feed_by_visitor = fields.Boolean(string='Is Feed By Visitor',
|
||||
default=False)
|
||||
|
||||
feed_visitor_message = fields.Char(string='Feeding Message',
|
||||
default='',
|
||||
required=False)
|
||||
|
||||
@api.onchange('is_feed_by_visitor')
|
||||
def _update_feed_visitor_message(self):
|
||||
if self.is_feed_by_visitor:
|
||||
self.feed_visitor_message = "Allow to feed the animal"
|
||||
else:
|
||||
self.feed_visitor_message = "Do not feed the animal!"
|
||||
|
||||
# add new method
|
||||
def get_basic_animal_info(self, id):
|
||||
record = self.browse(id)
|
||||
return str({
|
||||
"name": record.name,
|
||||
"gender": record.gender,
|
||||
"age": record.age,
|
||||
"feed_visitor_message": record.feed_visitor_message,
|
||||
})
|
||||
|
||||
def send_sms(self):
|
||||
if not self.description:
|
||||
raise ValidationError("Cannot send SMS due to empty description!")
|
||||
# https://esms.vn/SMSApi/ApiDetail
|
||||
url = "http://rest.esms.vn/MainService.svc/json/SendMultipleMessage_V4_post_json/"
|
||||
data = {
|
||||
"ApiKey": "9DF7A8FDDC5B8BEB99618CA9CC67DB", # <- registered to esms.vn
|
||||
"Content": self.description,
|
||||
"Phone": "0349684064",
|
||||
"SecretKey": "1A1D459D80F3B633300D7F79B1BEE1", # <- registered to esms.vn
|
||||
"IsUnicode": 1,
|
||||
# "Brandname": "minhng.info",
|
||||
"SmsType": 8,
|
||||
"Sandbox": 1, # 0=production (send real SMS), 1=sandbox mode (no SMS sent)
|
||||
}
|
||||
response = requests.post(url, json=data)
|
||||
# curl "http://rest.esms.vn/MainService.svc/json/GetSendStatus?RefId=be1eef90-c174-4c52-ada5-c3f0e8b9cddb127&ApiKey=9DF7A8FDDC5B8BEB99618CA9CC67DB&SecretKey=1A1D459D80F3B633300D7F79B1BEE1"
|
||||
raise UserError(str(response.json()))
|
||||
17
addons/zoo_plus/models/zoo_animal_backup.py
Normal file
17
addons/zoo_plus/models/zoo_animal_backup.py
Normal file
@ -0,0 +1,17 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo import api, fields, models, tools, _
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
|
||||
class ZooAnimalBackup(models.Model):
|
||||
_name = "zoo.animal.backup" # Tên mới
|
||||
_inherit = "zoo.animal" # Tên chính xác của model cha
|
||||
_description = "Backup animal model"
|
||||
|
||||
backup_code = fields.Char("Backup Code",
|
||||
required=True) # eg. BK200925, BK201001, ...
|
||||
|
||||
toy_ids = fields.Many2many(comodel_name='product.product',
|
||||
string="Toys BK",
|
||||
relation='animal_bk_product_toy_rel',
|
||||
column1='col_animal_bk_id',
|
||||
column2='col_product_id')
|
||||
18
addons/zoo_plus/my_client.py
Normal file
18
addons/zoo_plus/my_client.py
Normal file
@ -0,0 +1,18 @@
|
||||
from xml_rpc import XMLRPC_API, myprint
|
||||
|
||||
import ssl
|
||||
ssl._create_default_https_context = ssl._create_unverified_context
|
||||
|
||||
# please change the credential!
|
||||
ODOO_BACKEND = 'https://odoo.minhng.info' # http://localhost:10014
|
||||
ODOO_DB = 'CO200912-1'
|
||||
ODOO_USER = 'admin'
|
||||
ODOO_PASS = 'admin'
|
||||
|
||||
def main():
|
||||
client = XMLRPC_API(url=ODOO_BACKEND, db=ODOO_DB, username=ODOO_USER, password=ODOO_PASS)
|
||||
print(client.call(model_name="zoo.animal", method="get_basic_animal_info", params=[False, 3]))
|
||||
# {'name': 'Polar Bear', 'gender': 'female', 'age': 5, 'feed_visitor_message': ''}
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
2
addons/zoo_plus/security/ir.model.access.csv
Normal file
2
addons/zoo_plus/security/ir.model.access.csv
Normal file
@ -0,0 +1,2 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_zoo_animal_backup,access_zoo_animal_backup,model_zoo_animal_backup,base.group_user,1,1,1,1
|
||||
|
BIN
addons/zoo_plus/static/description/icon.png
Normal file
BIN
addons/zoo_plus/static/description/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
@ -50,7 +50,7 @@ addons_path = /mnt/extra-addons
|
||||
; ------
|
||||
; --load | Comma-separated list of server-wide modules.
|
||||
; ------
|
||||
; server_wide_modules = base,web
|
||||
; server_wide_modules = base,web,zoo
|
||||
|
||||
; ------
|
||||
; -D / --data-dir | Directory where to store Odoo data
|
||||
|
||||
Loading…
Reference in New Issue
Block a user