Nov 27th 2025

This commit is contained in:
mtpc4s9 2025-11-27 18:24:56 +07:00
parent 20e912c5d9
commit 37a29392e0
57 changed files with 1743 additions and 72 deletions

View File

@ -1 +1,3 @@
from . import models
from . import models
from . import wizard
from . import controllers

View File

@ -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'
],
}

View File

@ -0,0 +1 @@
from . import main

View 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)

View 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
1 name default_code type list_price standard_price uom_id/id categ_id/id description
2 Beef (Raw) FOOD001 consu 15.50 12.00 uom.product_uom_kgm product.product_category_all Fresh raw beef for carnivores
3 Chicken (Raw) FOOD002 consu 8.75 6.50 uom.product_uom_kgm product.product_category_all Fresh raw chicken meat
4 Fish (Whole) FOOD003 consu 12.00 9.00 uom.product_uom_kgm product.product_category_all Fresh whole fish for aquatic animals
5 Pork (Raw) FOOD004 consu 10.50 8.00 uom.product_uom_kgm product.product_category_all Fresh pork meat
6 Deer Meat FOOD005 consu 18.00 14.00 uom.product_uom_kgm product.product_category_all Premium deer meat for large carnivores
7 Grass Hay FOOD006 consu 3.50 2.00 uom.product_uom_kgm product.product_category_all Dried grass hay for herbivores
8 Alfalfa FOOD007 consu 4.25 2.50 uom.product_uom_kgm product.product_category_all High quality alfalfa
9 Fresh Vegetables FOOD008 consu 5.00 3.50 uom.product_uom_kgm product.product_category_all Mixed fresh vegetables
10 Fruits (Mixed) FOOD009 consu 6.50 4.50 uom.product_uom_kgm product.product_category_all Assorted fresh fruits
11 Bamboo FOOD010 consu 7.00 5.00 uom.product_uom_kgm product.product_category_all Fresh bamboo shoots and stalks
12 Fish Pellets FOOD011 consu 4.50 3.00 uom.product_uom_kgm product.product_category_all Specialized fish food pellets
13 Bird Seed Mix FOOD012 consu 3.00 2.00 uom.product_uom_kgm product.product_category_all Mixed seeds for birds
14 Insects (Live) FOOD013 consu 12.50 9.00 uom.product_uom_kgm product.product_category_all Live insects for insectivores
15 Rabbit (Whole) FOOD014 consu 9.00 7.00 uom.product_uom_kgm product.product_category_all Whole rabbit for medium carnivores
16 Squid FOOD015 consu 11.00 8.50 uom.product_uom_kgm product.product_category_all Fresh squid for marine animals
17 Krill FOOD016 consu 14.00 11.00 uom.product_uom_kgm product.product_category_all Frozen krill for aquatic animals
18 Lettuce FOOD017 consu 2.50 1.50 uom.product_uom_kgm product.product_category_all Fresh lettuce leaves
19 Carrots FOOD018 consu 2.00 1.20 uom.product_uom_kgm product.product_category_all Fresh carrots
20 Sweet Potato FOOD019 consu 3.00 2.00 uom.product_uom_kgm product.product_category_all Fresh sweet potatoes
21 Apples FOOD020 consu 4.00 3.00 uom.product_uom_kgm product.product_category_all Fresh apples

View 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
1 name description dob gender weight nickname is_alive is_purchased purchase_price creature_id/name cage_id/name
2 Simba Majestic lion with golden mane 2018-05-15 male 190.5 King True True 15000.0 Lion Savanna Habitat
3 Nala Beautiful lioness and pride leader 2019-03-20 female 130.0 Queen True True 12000.0 Lion Savanna Habitat
4 Shere Khan Bengal tiger with striking orange stripes 2017-08-10 male 220.0 Khan True True 25000.0 Tiger Rainforest Pavilion
5 Rajah Playful tiger cub learning to hunt 2022-11-05 female 45.5 Raja True False 0.0 Tiger Rainforest Pavilion
6 Dumbo Gentle giant elephant with big ears 2015-01-12 male 5400.0 Big Ears True True 50000.0 Elephant Safari Plains
7 Ellie Young elephant loves to play in water 2020-06-30 female 2800.0 Ellie True False 0.0 Elephant Safari Plains
8 Melman Tall giraffe with beautiful spots 2016-07-22 male 1200.0 Mel True True 18000.0 Giraffe Savanna Habitat
9 Gloria Graceful giraffe mother 2018-09-15 female 950.0 Glori True True 16000.0 Giraffe Savanna Habitat
10 Marty Striped zebra always energetic 2019-04-08 male 350.0 Stripey True True 8000.0 Zebra Safari Plains
11 Zara Young zebra with perfect stripes 2021-12-20 female 280.0 Z True False 0.0 Zebra Safari Plains
12 Skipper Penguin leader of the colony 2020-02-14 male 5.5 Skip True True 3000.0 Penguin Penguin Cove
13 Kowalski Smart penguin loves fish 2020-02-14 male 5.2 Kowl True True 3000.0 Penguin Penguin Cove
14 Private Young penguin still learning 2021-08-01 male 4.8 Priv True False 0.0 Penguin Penguin Cove
15 Rico Tough penguin protects the group 2020-02-14 male 5.7 Ric True True 3000.0 Penguin Penguin Cove
16 Aurora Beautiful white polar bear female 2016-11-30 female 450.0 Rory True True 35000.0 Polar Bear Polar Pavilion
17 Blizzard Large male polar bear king of arctic 2015-09-18 male 550.0 Bliz True True 40000.0 Polar Bear Arctic Zone
18 Flipper Friendly dolphin loves to jump 2019-05-25 male 180.0 Flip True True 20000.0 Dolphin Aquatic Center
19 Echo Dolphin with beautiful voice 2020-07-12 female 165.0 Echi True True 18000.0 Dolphin Tropical Reef
20 Jaws Impressive great white shark 2014-03-03 male 900.0 Big J True True 45000.0 Shark Aquatic Center
21 Marina Graceful reef shark 2018-10-20 female 220.0 Mari True True 15000.0 Shark Tropical Reef
22 Freedom Majestic bald eagle symbol of liberty 2017-06-04 male 6.5 Free True True 5000.0 Eagle Bird Sanctuary
23 Liberty Female eagle partner of Freedom 2018-04-15 female 5.8 Libby True True 4500.0 Eagle Bird Sanctuary
24 Zeus Powerful golden eagle 2016-01-20 male 7.2 Z-man True True 6000.0 Eagle Bird Sanctuary
25 Spirit Young eagle learning to fly 2022-05-10 male 4.5 Spir True False 0.0 Eagle Mountain Ridge
26 Leo Strong second alpha lion 2019-08-22 male 185.0 Leo True True 14000.0 Lion Savanna Habitat
27 Elsa Brave lioness hunter 2020-10-11 female 125.0 Ice Queen True True 11000.0 Lion Desert Dome
28 Tony Young energetic tiger 2021-03-15 male 95.0 T-Rex True False 0.0 Tiger Rainforest Pavilion
29 Shira Rare beautiful white tiger 2018-12-25 female 140.0 Snow True True 30000.0 Tiger Primate Paradise
30 Trunks Adorable baby elephant 2023-02-28 male 800.0 Trunk True False 0.0 Elephant Safari Plains
31 Patches Cute giraffe calf with unique spots 2023-01-05 female 320.0 Patchy True False 0.0 Giraffe Savanna Habitat

View 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
1 name capacity location cage_type area description active
2 Savanna Habitat 10 North Wing outdoor 500.5 Large outdoor habitat for African animals True
3 Rainforest Pavilion 8 East Wing indoor 350.0 Climate-controlled rainforest environment True
4 Arctic Zone 6 South Wing outdoor 400.0 Cold climate habitat with ice features True
5 Aquatic Center 15 West Wing aquarium 600.0 Large aquarium for marine life True
6 Bird Sanctuary 20 Central Area aviary 450.0 Open-air aviary with netting True
7 Desert Dome 5 North Wing indoor 300.0 Heated desert environment True
8 Primate Paradise 12 East Wing outdoor 380.0 Multi-level jungle gym for primates True
9 Reptile House 10 South Wing indoor 250.0 Temperature-controlled reptile habitat True
10 Polar Pavilion 4 West Wing indoor 320.0 Frozen tundra simulation True
11 Safari Plains 25 Central Area outdoor 800.0 Large plains for grazing animals True
12 Tropical Reef 20 East Wing aquarium 550.0 Coral reef ecosystem True
13 Mountain Ridge 8 North Wing outdoor 420.0 Rocky mountain terrain True
14 Nocturnal Gallery 6 South Wing indoor 180.0 Dark environment for nocturnal animals True
15 Penguin Cove 15 West Wing outdoor 400.0 Icy pool for penguins True
16 Butterfly Garden 30 Central Area aviary 200.0 Glass dome for butterflies True

View 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
1 name environment is_rare
2 Lion ground False
3 Tiger forest True
4 Elephant ground False
5 Giraffe ground False
6 Zebra ground False
7 Penguin cool False
8 Polar Bear cool True
9 Dolphin ocean False
10 Shark sea True
11 Eagle sky False

View File

@ -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

View File

@ -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

View 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'

View File

@ -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)

View File

@ -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)

View File

@ -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 ---

View 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 (nếu ).
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)")

View File

@ -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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
4 access_zoo_cage access_zoo_cage model_zoo_cage base.group_user 1 1 1 1
5 access_zoo_health_record access_zoo_health_record model_zoo_health_record base.group_user 1 1 1 1
6 access_zoo_diet_plan access_zoo_diet_plan model_zoo_diet_plan base.group_user 1 1 1 1
7 access_zoo_diet_line access_zoo_diet_line model_zoo_diet_line base.group_user 1 1 1 1
8 access_zoo_toy_add_wizard access_zoo_toy_add_wizard model_zoo_toy_add_wizard base.group_user 1 1 1 1
9 access_zoo_cage_update_wizard access_zoo_cage_update_wizard model_zoo_cage_update_wizard base.group_user 1 1 1 1
10 access_zoo_animal_meal access_zoo_animal_meal model_zoo_animal_meal base.group_user 1 1 1 1
11 access_zoo_animal_feeding_wizard access_zoo_animal_feeding_wizard model_zoo_animal_feeding_wizard base.group_user 1 1 1 1
12 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
13 access_zoo_husbandry_task access_zoo_husbandry_task model_zoo_husbandry_task base.group_user 1 1 1 1
14 access_zoo_husbandry_task_line access_zoo_husbandry_task_line model_zoo_husbandry_task_line base.group_user 1 1 1 1
15 access_zoo_cage_checklist_template access_zoo_cage_checklist_template model_zoo_cage_checklist_template base.group_user 1 1 1 1

View 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>

View File

@ -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 -->

View File

@ -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"/></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 -->

View File

@ -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 -->

View File

@ -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>

View 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 &amp; 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>

View File

@ -0,0 +1,3 @@
from . import toy_add
from . import cage_update
from . import animal_feeding

Binary file not shown.

Binary file not shown.

View 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
)

View 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 &amp; 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>

View 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'}

View 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>

View 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)

View 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>

View File

@ -0,0 +1 @@
from . import models

View 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,
}

Binary file not shown.

View File

@ -0,0 +1,2 @@
from . import zoo_animal
from . import zoo_animal_backup

View 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()))

View 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')

View 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()

View 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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_zoo_animal_backup access_zoo_animal_backup model_zoo_animal_backup base.group_user 1 1 1 1

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -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