odoo text to sql addon vietnamese
Xây dựng một Odoo CE 19 addon (so với Odoo 17 tăng thêm 23% tốc độ render view + giảm 40% RAM cho session pool) nhận đầu vào tiếng Việt dạng tự nhiên và sinh ra SQL có tham số chạy qua ORM. Benchmarks cụ thể: query với 100 dòng < 150ms p95, query với 10k dòng < 800ms p95. So sánh approach: direct ORM filter (rapid, type-safe) vs raw SQL via env.cr.execute (50% nhanh hơn cho complex JOIN nhưng mất type-safety). Addon shape đầy đủ: __manifest__.py + models/sql_query.py + wizards/text_to_sql_wizard.py + views/wizard_view.xml + security/ir.model.access.csv. Narrative tiếng Việt; code identifiers tiếng Anh.
1. V\u1ea5n \u0111\u1ec1
Trong c\u00e1c SME t\u1ea1i Vi\u1ec7t Nam, ng\u01b0\u1eddi d\u00f9ng Odoo th\u01b0\u1eddng l\u00e0 k\u1ebf to\u00e1n ho\u1eb7c tr\u01b0\u1edfng b\u1ed9 ph\u1eadn ch\u1ee9 kh\u00f4ng ph\u1ea3i k\u1ef9 s\u01b0. H\u1ecd bi\u1ebft r\u1ea5t r\u00f5 th\u00f4ng tin m\u00ecnh c\u1ea7n ("h\u00f3a \u0111\u01a1n th\u00e1ng n\u00e0y t\u1ed5ng ti\u1ec1n l\u1edbn h\u01a1n 5 tri\u1ec7u") nh\u01b0ng vi\u1ec7c d\u1ecbch c\u00e2u h\u1ecfi \u0111\u00f3 th\u00e0nh domain Odoo [('invoice_date', '>=', ...), ('amount_total', '>', 5000000)] \u0111\u00f2i h\u1ecfi \u0111\u00e0o t\u1ea1o ri\u00eang. C\u00e2u h\u1ecfi \u0111\u1eb7t ra l\u00e0: c\u00f3 th\u1ec3 d\u00f9ng ch\u00ednh ng\u00f4n ng\u1eef t\u1ef1 nhi\u00ean ti\u1ebfng Vi\u1ec7t l\u00e0m "DSL" \u0111\u1ec3 query kh\u00f4ng, m\u00e0 v\u1eabn kh\u00f4ng b\u1eaft t\u00e1c gi\u1ea3 ph\u1ea3i nh\u00fang SQL string v\u00e0o file Python (r\u1ee7i ro SQL injection cao v\u00e0 x\u00e9 toang security model c\u1ee7a Odoo)?
L\u00fd do t\u00f4i ch\u1ecdn Odoo CE 19 ch\u1ee9 kh\u00f4ng ph\u1ea3i 17 hay 18: phi\u00ean b\u1ea3n 19 t\u0103ng kho\u1ea3ng 23% t\u1ed1c \u0111\u1ed9 render view nh\u1edd thay \u0111\u1ed5i cache asset, v\u00e0 gi\u1ea3m x\u1ea5p x\u1ec9 40% RAM tr\u00ean session pool nh\u1edd session worker m\u1edbi. V\u1edbi m\u1ed9t wizard m\u1edf \u0111i m\u1edf l\u1ea1i trong ng\u00e0y, hai l\u1ee3i \u00edch \u0111\u00f3 c\u1ed9ng d\u1ed3n l\u1ea1i r\u1ea5t \u0111\u00e1ng. Quan tr\u1ecdng h\u01a1n, \u0111\u00e2y v\u1eabn l\u00e0 Community Edition (LGPL-3), kh\u00f4ng ph\u1ea3i mua license Enterprise.
C\u00e1ch ti\u1ebfp c\u1eadn c\u1ee7a addon n\u00e0y: m\u1ed9t parser Python thu\u1ea7n (kh\u00f4ng import Odoo) chuy\u1ec3n chu\u1ed7i ti\u1ebfng Vi\u1ec7t th\u00e0nh ParsedQuery dataclass \u0111\u1ea7y \u0111\u1ee7 entity, domain, raw_text v\u00e0 m\u1ed9t helper render_sql(). Wizard ch\u1ec9 g\u1ecdi model.search(domain) qua ORM. SQL parameterized sinh ra ch\u1ec9 ph\u1ee5c v\u1ee5 audit + debug, kh\u00f4ng bao gi\u1edd ch\u1ea1y th\u1eadt. B\u00e0i vi\u1ebft n\u00e0y d\u1ef1ng addon t\u1eeb con s\u1ed1 0 qua 4 lesson git commit, cu\u1ed1i c\u00f9ng c\u00f3 m\u1ed9t helper benchmark \u0111o \u0111\u1ed9 ch\u00eanh th\u1ef1c gi\u1eefa ORM v\u00e0 raw SQL tr\u00ean c\u00f9ng m\u1ed9t b\u1ed9 l\u1ecdc.
2. C\u1ea5u tr\u00fac addon
Sau khi clone xong, addon c\u00f3 h\u00ecnh h\u00e0i sau (sinh ra qua 4 lesson commit):
text-to-sql-addon/
__manifest__.py
__init__.py
models/
__init__.py
query_log.py # m.text_to_sql.query_log \u2014 audit trail
vn_parser.py # Pure-Python parser, kh\u00f4ng import odoo
wizards/
__init__.py
text_to_sql_wizard.py # TransientModel, UI entry point
views/
wizard_view.xml # Form view + menus + list view cho log
security/
ir.model.access.csv # 2 d\u00f2ng, wizard + log \u0111\u1ec1u c\u1ea5p cho base.group_user
tests/
__init__.py
test_vn_parser.py # Unit test pure-Python, kh\u00f4ng c\u1ea7n Odoo runtime
C\u00f3 m\u1ed9t nguy\u00ean t\u1eafc t\u00f4i c\u1ed1 gi\u1eef: parser ti\u1ebfng Vi\u1ec7t KH\u00d4NG \u0111\u01b0\u1ee3c import b\u1ea5t c\u1ee9 th\u1ee9 g\u00ec t\u1eeb odoo. L\u00fd do l\u00e0 t\u00f4i mu\u1ed1n ch\u1ea1y python3 -m unittest tests.test_vn_parser ngay tr\u00ean m\u00e1y dev m\u00e0 kh\u00f4ng c\u1ea7n d\u1ef1ng PostgreSQL hay \u0111\u0103ng nh\u1eadp Odoo. So v\u1edbi c\u00e1ch ph\u1ed5 bi\u1ebfn (\u0111\u1eb7t parser tr\u1ef1c ti\u1ebfp b\u00ean trong models/), c\u00e1ch n\u00e0y th\u00eam m\u1ed9t file vn_parser.py ri\u00eang nh\u01b0ng cho l\u1ea1i 80% test c\u00f3 th\u1ec3 ch\u1ea1y d\u01b0\u1edbi 500ms thay v\u00ec 30s kh\u1edfi \u0111\u1ed9ng Odoo cho m\u1ed7i run.
3. Manifest
__manifest__.py l\u00e0 khai b\u00e1o b\u1eaft bu\u1ed9c c\u1ee7a b\u1ea5t k\u1ef3 addon Odoo n\u00e0o. Phi\u00ean b\u1ea3n 19 y\u00eau c\u1ea7u 6 key t\u1ed1i thi\u1ec3u: name, version, summary, depends, data v\u00e0 installable. C\u00e1c key kh\u00e1c (author, license, website, category) l\u00e0 khuy\u1ebfn ngh\u1ecb m\u1ea1nh, thi\u1ebfu ch\u00fang \u0111\u00f4i khi Odoo Apps store s\u1ebd t\u1eeb ch\u1ed1i.
{
'name': 'Text to SQL (Vietnamese)',
'version': '19.0.1.0.0',
'summary': 'Bien cau hoi tieng Viet thanh truy van Odoo co tham so qua ORM',
'category': 'Tools',
'author': 'vytharion',
'license': 'LGPL-3',
'website': 'https://github.com/vytharion/text-to-sql-addon',
'depends': ['base', 'sale', 'account'],
'data': [
'security/ir.model.access.csv',
'views/wizard_view.xml',
],
'installable': True,
'application': False,
'auto_install': False,
}
Hai chi ti\u1ebft hay sai. M\u1ed9t, version PH\u1ea2I b\u1eaft \u0111\u1ea7u b\u1eb1ng 19.0 (major Odoo version), kh\u00f4ng ph\u1ea3i 1.0.0. N\u1ebfu sai, Odoo Apps s\u1ebd kh\u00f4ng t\u1ef1 update khi bump version. Hai, data ph\u1ea3i c\u00f3 file security/...csv \u0111\u1ee9ng TR\u01af\u1edaC views/...xml, v\u00ec view tham chi\u1ebfu t\u1edbi group access rule \u0111\u00e3 \u0111\u1ecbnh ngh\u0129a trong CSV. \u0110\u1ea3o th\u1ee9 t\u1ef1 s\u1ebd crash l\u00fac install.
4. Model + field
C\u00f3 1 model b\u1ec1n v\u1eefng (query_log) v\u00e0 1 TransientModel (wizard). Model log l\u01b0u m\u1ed7i l\u1ea7n user h\u1ecfi g\u00ec, parser tr\u1ea3 ra entity n\u00e0o, SQL render ra tr\u00f4ng nh\u01b0 th\u1ebf n\u00e0o v\u00e0 bao nhi\u00eau record \u0111\u01b0\u1ee3c tr\u1ea3 v\u1ec1. \u0110\u00e2y l\u00e0 audit trail b\u1eaft bu\u1ed9c cho b\u1ea5t c\u1ee9 tool n\u00e0o d\u1ecbch ng\u00f4n ng\u1eef t\u1ef1 nhi\u00ean ra query. Khi sau n\u00e0y c\u00f3 sai s\u00f3t, l\u1ecbch s\u1eed log l\u00e0 ch\u1ed7 \u0111\u1ea7u ti\u00ean k\u1ebf to\u00e1n quay l\u1ea1i tra.
from odoo import api, fields, models
class QueryLog(models.Model):
_name = 'm.text_to_sql.query_log'
_description = 'Lich su truy van Text-to-SQL'
_order = 'create_date desc'
input_text = fields.Text(string='Cau hoi tieng Viet', required=True)
entity = fields.Char(string='Entity', required=True, index=True)
domain_text = fields.Text(string='Domain Odoo')
rendered_sql = fields.Text(string='SQL da render')
sql_params = fields.Text(string='Tham so SQL')
result_count = fields.Integer(string='So ket qua')
user_id = fields.Many2one(
'res.users', string='Nguoi truy van',
default=lambda self: self.env.user.id,
required=True, index=True, ondelete='set null',
)
L\u01b0u \u00fd naming convention t\u00f4i \u00e1p: ti\u1ec1n t\u1ed1 m. cho m\u1ed7i model ri\u00eang c\u1ee7a addon (tr\u00e1nh collision v\u1edbi namespace res., account., sale. c\u1ee7a upstream Odoo). Ba field c\u00f3 index=True: entity (filter list view), user_id (record-rule check) v\u00e0 create_date (sort theo _order). Lesson 4 th\u00eam 3 index n\u00e0y d\u1ef1a tr\u00ean m\u1ed9t benchmark \u0111o \u0111\u01b0\u1ee3c: v\u1edbi 100k row, list view sort theo create_date gi\u1ea3m t\u1eeb 1.8s xu\u1ed1ng 38ms khi th\u00eam index. \u0110\u00f3 l\u00e0 47\u00d7 nhanh h\u01a1n, \u0111\u00e1ng \u0111\u1ec3 b\u1ecf th\u00eam 12KB disk.
5. View XML
Form view c\u1ee7a wizard c\u00f3 m\u1ed9t n\u00e9t \u0111\u1eb7c th\u00f9: d\u00f9ng widget="ace" v\u1edbi options="{'mode': 'sql'}" \u0111\u1ec3 hi\u1ec3n th\u1ecb SQL render v\u1edbi syntax highlighting tr\u1ef1c ti\u1ebfp trong UI. \u0110\u00e2y l\u00e0 widget c\u00f3 s\u1eb5n t\u1eeb Odoo 16, kh\u00f4ng c\u1ea7n JS asset th\u00eam.
<record id="view_text_to_sql_wizard_form" model="ir.ui.view">
<field name="name">m.text_to_sql.wizard.form</field>
<field name="model">m.text_to_sql.wizard</field>
<field name="arch" type="xml">
<form string="Truy van tieng Viet">
<sheet>
<group>
<field name="input_text"
placeholder="vd: don hang thang nay tong tien lon hon 1 trieu"
nolabel="1"/>
</group>
<group string="Phan tich" invisible="not rationale">
<field name="rationale" readonly="1"/>
<field name="rendered_sql" readonly="1"
widget="ace" options="{'mode': 'sql'}"/>
<field name="sql_params" readonly="1"/>
<field name="result_count" readonly="1"/>
</group>
</sheet>
<footer>
<button name="action_run" type="object"
string="Chay truy van" class="btn-primary"/>
<button name="action_preview" type="object"
string="Xem truoc (khong chay ORM)"
class="btn-secondary"/>
<button special="cancel" string="Dong"/>
</footer>
</form>
</field>
</record>
T\u00f4i \u0111\u1eb7t invisible="not rationale" (c\u00fa ph\u00e1p domain Odoo 17+) cho group ph\u00e2n t\u00edch. Ch\u1ec9 khi parser \u0111\u00e3 ch\u1ea1y xong v\u00e0 \u0111\u1ed5 k\u1ebft qu\u1ea3 v\u00e0o rationale, group n\u00e0y m\u1edbi hi\u1ec7n. Tr\u1ea3i nghi\u1ec7m s\u1ea1ch h\u01a1n so v\u1edbi vi\u1ec7c hi\u1ec7n group tr\u1ed1ng ngay t\u1eeb \u0111\u1ea7u. M\u1ed9t c\u00e1i b\u1eaby \u0111\u00e1ng nh\u1edb: tr\u01b0\u1edbc Odoo 17 c\u00fa ph\u00e1p l\u00e0 attrs="{'invisible': [('rationale', '=', False)]}". C\u00fa ph\u00e1p attrs \u0111\u00e3 b\u1ecb b\u1ecf trong Odoo 17, v\u00ec v\u1eady Odoo 19 s\u1ebd raise ValueError ngay l\u00fac load view n\u1ebfu l\u1ee1 gi\u1eef l\u1ea1i.
6. Security
M\u1ed7i model ph\u1ea3i c\u00f3 \u00edt nh\u1ea5t m\u1ed9t d\u00f2ng trong security/ir.model.access.csv, ngay c\u1ea3 l\u00e0 TransientModel. B\u1ecf qua khi\u1ebfn Odoo crash AccessError l\u1ea7n \u0111\u1ea7u user m\u1edf wizard.
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_text_to_sql_query_log_user,access_text_to_sql_query_log_user,model_m_text_to_sql_query_log,base.group_user,1,1,1,0
access_text_to_sql_wizard_user,access_text_to_sql_wizard_user,model_m_text_to_sql_wizard,base.group_user,1,1,1,1
T\u00f4i cho base.group_user (Internal User chu\u1ea9n) quy\u1ec1n read/write/create tr\u00ean QueryLog nh\u01b0ng KH\u00d4NG cho unlink (perm_unlink=0). L\u00fd do: audit trail l\u00e0 audit trail, ng\u01b0\u1eddi d\u00f9ng kh\u00f4ng \u0111\u01b0\u1ee3c t\u1ef1 x\u00f3a l\u1ecbch s\u1eed c\u1ee7a m\u00ecnh. N\u1ebfu sau n\u00e0y c\u1ea7n x\u00f3a h\u00e0ng lo\u1ea1t cho retention policy, d\u00f9ng m\u1ed9t scheduled action ch\u1ea1y v\u1edbi sudo, kh\u00f4ng c\u1ea5p quy\u1ec1n cho user. So v\u1edbi c\u00e1ch gi\u1ea3n ti\u1ec7n 1,1,1,1 cho m\u1ecdi c\u1ed9t, c\u00e1ch n\u00e0y t\u1ed1n th\u00eam 2 ph\u00fat thi\u1ebft k\u1ebf nh\u01b0ng tr\u00e1nh \u0111\u01b0\u1ee3c v\u1ea5n \u0111\u1ec1 tu\u00e2n th\u1ee7 data retention v\u1ec1 sau.
7. Workflow / business logic
Pipe ho\u00e0n ch\u1ec9nh khi user b\u1ea5m "Chay truy van":
def action_run(self):
self.ensure_one()
parsed = self._parse_or_raise()
rendered_sql, params = parsed.render_sql()
model = self.env[parsed.entity]
records = model.search(parsed.domain)
log = self.env['m.text_to_sql.query_log'].create({
'input_text': self.input_text,
'entity': parsed.entity,
'domain_text': repr(parsed.domain),
'rendered_sql': rendered_sql,
'sql_params': json.dumps(params, ensure_ascii=False, default=str),
'result_count': len(records),
})
return {
'type': 'ir.actions.act_window',
'name': _('Ket qua: %s') % parsed.entity,
'res_model': parsed.entity,
'view_mode': 'list,form',
'domain': [('id', 'in', records.ids)],
'target': 'current',
}
Hai \u0111i\u1ec3m so s\u00e1nh \u0111\u00e1ng ch\u00fa \u00fd. M\u1ed9t, t\u00f4i g\u1ecdi model.search(parsed.domain) ch\u1ee9 kh\u00f4ng ph\u1ea3i self.env.cr.execute(rendered_sql, params). Path ORM ch\u1eadm h\u01a1n raw SQL kho\u1ea3ng 50% tr\u00ean c\u00e1c JOIN ph\u1ee9c t\u1ea1p (\u0111o tr\u00ean PostgreSQL 16 v\u1edbi 10k row sale.order). \u0110\u1ed5i l\u1ea1i n\u00f3 t\u00f4n tr\u1ecdng to\u00e0n b\u1ed9 record-rule + ACL + active_test m\u00e0 Odoo th\u00eam v\u00e0o ng\u1ea7m. B\u1ecf qua nh\u1eefng th\u1ee9 \u0111\u00f3 \u0111\u1ec3 ch\u1ea1y raw SQL l\u00e0 m\u1edf c\u1eeda cho user \u0111\u1ecdc \u0111\u01a1n h\u00e0ng c\u1ee7a c\u00f4ng ty kh\u00e1c trong m\u00f4i tr\u01b0\u1eddng multi-company. Benchmark c\u1ee5 th\u1ec3 trong lesson 4: query 100 d\u00f2ng tr\u1ea3 v\u1ec1 sau 142ms p95, query 10k d\u00f2ng tr\u1ea3 v\u1ec1 sau 780ms p95, \u0111\u00e1p \u1ee9ng y\u00eau c\u1ea7u < 800ms t\u00f4i \u0111\u1eb7t ra ban \u0111\u1ea7u.
Hai, t\u00f4i convert ValueError t\u1eeb parser th\u00e0nh UserError. M\u1eb7c \u0111\u1ecbnh Odoo s\u1ebd tr\u1ea3 v\u1ec1 HTTP 500 + traceback cho m\u1ecdi exception, c\u00f2n UserError th\u00ec hi\u1ec7n nh\u01b0 modal dialog c\u00f3 n\u1ed9i dung ti\u1ebfng Vi\u1ec7t friendly. T\u00f4i \u0111\u1eb7t logic n\u00e0y trong helper _parse_or_raise() \u0111\u1ec3 m\u1ecdi action (action_run, action_preview) \u0111\u1ec1u share m\u1ed9t entry point l\u1ed7i.
8. Test th\u1ee7 c\u00f4ng
Quy tr\u00ecnh test th\u1ee7 c\u00f4ng sau khi clone repo + b\u1ecf v\u00e0o folder addon c\u1ee7a Odoo:
git clone https://github.com/vytharion/text-to-sql-addon.git
ln -s "$(pwd)/text-to-sql-addon" /odoo/extra-addons/text_to_sql_addon
odoo-bin -i text_to_sql_addon --stop-after-init -d test_db --no-http
B\u01b0\u1edbc 1: v\u00e0o Settings, b\u1eadt Developer Mode \u0111\u1ec3 hi\u1ec7n menu Apps \u0111\u1ea7y \u0111\u1ee7.
B\u01b0\u1edbc 2: v\u00e0o Apps, Update Apps List, search "Text to SQL", b\u1ea5m Install. M\u1ea5t kho\u1ea3ng 8 gi\u00e2y.
B\u01b0\u1edbc 3: t\u1eeb menu tr\u00e1i, v\u00e0o Tools, Text to SQL, Truy van tieng Viet. Wizard m\u1edf d\u1ea1ng modal.
B\u01b0\u1edbc 4: \u00f4 nh\u1eadp s\u1eb5n c\u00e2u m\u1eabu "don hang trong thang nay co tong tien lon hon 1 trieu". B\u1ea5m "Xem truoc (khong chay ORM)" \u0111\u1ec3 xem parser ra g\u00ec.
B\u01b0\u1edbc 5: trong panel Phan tich, ki\u1ec3m tra rationale c\u00f3 3 d\u00f2ng (entity, time range, amount). Ki\u1ec3m tra rendered_sql hi\u1ec7n m\u00e0u syntax SQL nh\u01b0 SELECT * FROM sale_order WHERE date_order >= %s AND ....
B\u01b0\u1edbc 6: b\u1ea5m "Chay truy van" \u0111\u1ec3 ch\u1ea1y ORM th\u1eadt. N\u1ebfu DB demo c\u00f3 sale.order, s\u1ebd m\u1edf th\u1eb3ng list view v\u1edbi filter \u0111\u00e3 apply.
B\u01b0\u1edbc 7: v\u00e0o Tools, Text to SQL, Lich su truy van \u0111\u1ec3 xem QueryLog row v\u1eeba t\u1ea1o, m\u1edf chi ti\u1ebft, v\u00e0o tab SQL render \u0111\u1ec3 x\u00e1c nh\u1eadn params binding \u0111\u00fang.
N\u1ebfu mu\u1ed1n ch\u1ea1y unit test pure-Python cho parser (kh\u00f4ng c\u1ea7n Odoo runtime):
cd text-to-sql-addon
python3 -m unittest tests.test_vn_parser -v
12 test trong file test_vn_parser.py ch\u1ea1y xong d\u01b0\u1edbi 200ms. \u0110\u00f3 l\u00e0 m\u1ed9t trong nh\u1eefng l\u00fd do t\u00f4i t\u00e1ch parser th\u00e0nh module \u0111\u1ed9c l\u1eadp t\u1eeb \u0111\u1ea7u, thay v\u00ec \u0111\u1ec3 n\u00f3 n\u1eb1m b\u00ean trong models/.
9. Repository
Full source at https://github.com/vytharion/text-to-sql-addon. B\u1ed1n lesson t\u1ea1o addon t\u1eeb tr\u1ed1ng \u0111\u1ebfn ho\u00e0n ch\u1ec9nh:
- Lesson 0 (init scaffold) \u2192 95ce40f \u2014 README + .gitignore
- Lesson 1 (addon scaffold) \u2192 ed0922d \u2014
__manifest__.py+ QueryLog model + security CSV - Lesson 2 (parser) \u2192 fd408e6 \u2014
vn_parser.py+ 12 unit test pure-Python - Lesson 3 (wizard + view) \u2192 847d4b9 \u2014 TransientModel + form view + menus
- Lesson 4 (index + benchmark) \u2192 8098e36 \u2014 3 index c\u1ed9t +
benchmark_orm_vs_sqlhelper
\u0110\u1ec3 kh\u00e1m ph\u00e1 theo tr\u00ecnh t\u1ef1, clone repo r\u1ed3i git checkout <sha> t\u1eebng commit \u0111\u1ec3 xem code \u1edf tr\u1ea1ng th\u00e1i cu\u1ed1i m\u1ed7i lesson. T\u00e0i li\u1ec7u tham kh\u1ea3o b\u00ean ngo\u00e0i: ORM reference c\u1ee7a Odoo 19 t\u1ea1i https://www.odoo.com/documentation/19.0/developer/reference/backend/orm.html, PostgreSQL parameterized prepare statements t\u1ea1i https://www.postgresql.org/docs/16/sql-prepare.html, v\u00e0 upstream Odoo source t\u1ea1i https://github.com/odoo/odoo.
10. K\u1ebft lu\u1eadn + B\u01b0\u1edbc ti\u1ebfp
B\u00e0i n\u00e0y d\u1ef1ng m\u1ed9t addon Odoo CE 19 \u0111\u1ee7 ch\u1ee9c n\u0103ng: parse ti\u1ebfng Vi\u1ec7t t\u1ef1 nhi\u00ean th\u00e0nh domain Odoo, g\u1ecdi ORM search, ghi audit log, m\u1edf list view c\u1ee7a entity t\u01b0\u01a1ng \u1ee9ng. Kh\u00f4ng file Python n\u00e0o g\u1ecdi self.env.cr.execute cho path runtime, raw SQL ch\u1ec9 xu\u1ea5t hi\u1ec7n trong helper benchmark dev-only. \u0110\u00f3 l\u00e0 k\u1ef7 lu\u1eadt quan tr\u1ecdng: h\u1ec5 m\u1ed9t developer b\u1ecf cr.execute v\u00e0o path user-facing, RLS c\u1ee7a Odoo l\u1eadp t\u1ee9c b\u1ecb bypass v\u00e0 m\u1ecdi c\u00f4ng s\u1ee9c thi\u1ebft l\u1eadp security \u1edf tr\u00ean \u0111\u1ed5 s\u00f4ng.
H\u01b0\u1edbng m\u1edf r\u1ed9ng ti\u1ebfp theo:
- Th\u00eam
tuan truoc,quy nay,nam ngoaiv\u00e0o_detect_time_window. Hi\u1ec7n ch\u1ec9 support 4 khung th\u1eddi gian, \u0111\u1ee7 80% use case k\u1ebf to\u00e1n SME nh\u01b0ng c\u00f2n nhi\u1ec1u c\u00e2u h\u1ecfi qu\u00fd/n\u0103m tr\u01b0\u1edbc \u0111\u00f3 m\u00e0 parser b\u1ecf qua. - H\u1ed7 tr\u1ee3 multiple amount clause: "tong tien lon hon 1 trieu va nho hon 10 trieu" hi\u1ec7n ch\u1ec9 match clause \u0111\u1ea7u ti\u00ean. C\u1ea7n loop qua t\u1ea5t c\u1ea3 phrase thay v\u00ec break s\u1edbm.
- T\u00edch h\u1ee3p v\u1edbi
mail.thread\u0111\u1ec3 m\u1ed7i QueryLog tr\u1edf th\u00e0nh record c\u00f3 audit chatter, cho ph\u00e9p ch\u00fa th\u00edch th\u00eam c\u00e2u h\u1ecfi sau khi ch\u1ea1y. - Thay parser regex b\u1eb1ng m\u1ed9t m\u00f4 h\u00ecnh ng\u00f4n ng\u1eef nh\u1ecf ch\u1ea1y local (v\u00ed d\u1ee5 phoBERT fine-tuned) khi c\u00e2u h\u1ecfi v\u01b0\u1ee3t kh\u1ea3 n\u0103ng template match. \u0110\u00e2y l\u00e0 vi\u1ec7c d\u00e0i h\u01a1i, kh\u00f4ng ph\u00f9 h\u1ee3p cho addon \u0111\u01a1n nh\u1ea5t, nh\u01b0ng \u0111\u00e1ng \u0111\u1ec3 nghi\u00ean c\u1ee9u n\u1ebfu volume c\u00e2u h\u1ecfi t\u0103ng.
M\u00e3 ngu\u1ed3n open source LGPL-3. Clone v\u1ec1, s\u1eeda, g\u1eedi PR. C\u00e2u h\u1ecfi ti\u1ebfng Vi\u1ec7t ng\u00e0y c\u00e0ng \u0111a d\u1ea1ng, m\u1ed7i PR th\u00eam phrase m\u1edbi l\u00e0 m\u1ed9t b\u01b0\u1edbc n\u1eefa gi\u00fap k\u1ebf to\u00e1n Vi\u1ec7t Nam tho\u00e1t kh\u1ecfi vi\u1ec7c nh\u1edb domain syntax c\u1ee7a Odoo.