OpenERP Technical Memento

Thảo luận trong 'Dịch Thuật (l18n)' bắt đầu bởi admin, 22/1/13.

  1. admin Administrator

    Open Source RAD with OpenObject

    LỜI MỞ ĐẦU: OpenERP là một phần mềm quản lý doanh nghiệp hiện đại, phát hành theo giấy phép AGPL, và có đầy đủ các tính năng CRM, Nhân Sự, Bán Hàng, Kế Toán, Sản Xuất, Quản Lý Kho, Quản Lý Dự Án, ... Nó được phát triển dựa trên framework OpenObject, một framework hướng mô đun, có khả năng mở rộng, và là một nền tảng trực quan được viết bằng ngôn ngữ Python để giúp phát triển các ứng dụng một cách nhanh chóng.


    OpenObject có một bộ công cụ đầy đủ và hướng mô đun để xây dựng các ứng dụng nhanh chóng: hỗ trợ Object-Relationship Mapping (ORM), mô hình Model-View-Controller (MVC), một hệ thống xuất báo cáo, đa ngôn ngữ, và nhiều thứ khác nữa.
    Python là một ngôn ngữ lập trình bậc cao, lý tưởng cho RAD (Rapid Application Development), kết hợp sức mạnh với cú pháp rõ ràng, và được thiết kế tối ưu giúp giữ cho phần lõi luôn nhỏ gọn.
    Mẹo: Một số liên kết hữu ích:
    • Trang chủ và download : www.openerp.com
    • Tài liệu sử dụng và kỹ thuật: doc.openerp.com
    • Các tài nguyên của cộng đồng: www.launchpad.net /open-object
    • Máy chủ được tích hợp tại : test.openobject.com
    • Học Python: doc.python.org
    • Nền tảng OpenERP E-Learning: edu.openerp.com
    Cài đặt OpenERP

    OpenERP được phân phối theo gói / bộ cài đặt cho hầu hết các nền tảng, và bạn cũng có thể cài đặt mã nguồn trên bất cứ nền tảng nào.
    Kiến trúc OpenERP


    [IMG]
    OpenERP sử dụng mô hình client-server nổi tiếng, do đó sẽ có các phần mềm đóng vai trò là máy khách và máy chủ, tùy thuộc vào cấu hình của bạn.
    Phần mềm máy khách (Client software)
    OpenERP cung cấp một phần mềm máy khách (gọi là GTK Client) trên tất cả các nền tảng, và một giao diện web có thể truy cập bằng bất kỳ trình duyệt hiện đại nào.

    Mẹo: Quá trình cài đặt
    Hãy luôn kiểm tra các tài liệu hướng dẫn (trong bản phân phối hoặc trên website) để cập nhật thông tin mới nhất về bản cài đặt; vì bản cài đặt luôn được phát triển và phụ thuộc vào nhiều phần mềm khác. Xem tại http://doc.openerp.com/install.
    Gói cài đặt
    • Windows: trên website chúng tôi có sẵn các bản cài đặt all-in-one, hoặc các bản cài đặt riêng lẻ dành cho: server, client, webserver.
    • Linux: bạn có thể cài các gói OpenERP-server và OpenERP-client bằng cách trình quản lý gói cài đặt như Synaptic của Ubuntu.
    • Mac: ở trang devteam.taktik.be có các gói cài đặt GTK Client, cũng như các bài hướng dẫn để cài openerp-server.
    Cài đặt từ mã nguồn
    Có 2 cách: tải gói mã nguồn ngay trên website của OpenERP hoặc dùng Bazaar (phần mềm quản lý phiên bản mã nguồn phân tán) để tải trực tiếp từ Launchpad. Bạn cũng cần phải cài đặt các chương trình phụ thuộc khác (như PostgreSQL và một vài thư viện Python – xem tài liệu tại doc.openerp.com)
    Gợi ý biên dịch: Vì OpenERP được viết bằng Python, nên bạn không cần phải biên dịch trước khi chạy.
    Các bước tải mã nguồn bằng bazaar(các bản Linux trên nền Debian):
    Mã (text):
    1. $ sudo apt-get install bzr # cài đặt phiên bản bazaar
    2. $ bzr branch lp:openerp # gọi trình cài đặt nguồn
    3. $ cd openerp && python ./bzr_set.py # nạp mã và cài đặt
    Khởi tạo Database
    Sau khi cài đặt, chạy các chương trình máy chủ và máy khách. Từ phần mềm GTK Client, vào File→Databases→New Database để tạo ra một cơ sở dữ liệu mới (mật khẩu mặc định của quản trị viên là admin). Mỗi csdl đều có các mô đun và cấu hình riêng của nó, và bạn cũng có thể chọn dữ liệu demo trong lúc tạo csdl.
    Xây dựng một mô đun OpenERP: mô đun idea

    GiỚI THIỆU: Các code mẫu được dùng trong tài liệu này được trích từ mô đun idea. Khi bạn nảy ra ý tưởng nào đó, mà chưa có thể thực hiện ngay, thay vì ghi vào một chỗ nào đó (rất dễ quên) thì mô đun này sẽ giúp bạn ghi nhận, sắp xếp lại và đánh giá chúng.
    Lưu ý: Phát triển hướng mô đun
    OpenObject được phát triển theo hướng mô đun, để giúp cho việc bảo trì và phát triển dễ dàng và nhanh chóng hơn. Mỗi mô đun sẽ đưa ra các tính năng độc lập, mức độ trừu tượng thích hợp, và mô hình MVC rõ ràng.
    Thành phần của một mô đun:
    Một mô đun có thể chứa các phần sau:
    • Business object (đối tượng): khai báo như các lớp trong Python; được mở rộng từ lớp osv.osv trong OpenObject; OpenObject sẽ quản lý các lớp này.
    • Data (dữ liệu): các tập tin XML/CSV sẽ chứa các khai báo, định nghĩa cho các views và workflow; các dữ liệu cấu hình (như các tham số của mô đun) và các dữ liệu mẫu (các dữ liệu mẫu này có thể có hoặc không, nhưng chúng tôi khuyên bạn nên tạo để giúp cho việc test, trong mô đun idea mẫu này cũng có các dữ liệu mẫu)
    • Wizards: những hộp thoại tương tác để hỗ trợ người dùng thực hiện một tác vụ, hành động nào đó;
    • Report (báo cáo): tạo báo cáo kinh doanh dạng HTML, ODT, PDF từ các mẫu RML (định dạng XML), Mako hoặc các mẫu báo cáo OpenOffice.
    Cấu trúc của một mô đun
    Các mô đun được chứa tại thư mục server/bin/addons trong chỗ cài đặt OpenERP server.
    Lưu ý: Bạn có thể khai báo thư mục addons riêng của bạn trong tập tin cấu hình của OpenERP (được truyền cho máy chủ bằng tham số tùy chọn -c) bằng cách sử dụng tham số tùy chọn addons_path.
    Mã (python):
    1. addons/
    2.     | - idea/              # tên thư mục mô đun
    3.         | - demo/          # chứa các dữ liệu demo và test
    4.         | - i18n/          # chứa các file dịch thuật
    5.         | - report/        # các báo cáo
    6.         | - security/      # định nghĩa, khai báo các nhóm người dùng và quyền truy cập
    7.         | - view/          # khai báo các views (form, tree view), các menu và actions
    8.         | - wizard/        # định nghĩa các hộp thoại wizard
    9.         | - workflow/      # định nghĩa workflow
    10.         | - __init__.py    # file khởi tạo package Python (bắt buộc phải có)
    11.         | - __terp__.py    # khai báo mô đun (bắt buộc phải có)
    12.         | - idea.py        # các lớp python, các đối tượng trong mô đun
    Tập tin __ init__.py là tập tin mô tả mô đun Python, bởi vì một mô đun OpenERP cũng là một mô đun Python thông thường nên tập tin này bắt buộc phải có.
    Mã (text):
    1. # import tất cả các file, thư mục code python mà bạn muốn dùng
    2. import idea, wizard, report
    3.  
    __ terp__.py (các phiên bản mới dùng __openerp__.py) là file mô tả mô đun OpenERP và chứa một dictionary (một kiểu dữ liệu trong Python) với khai báo các thông tin của mô đun: tên , các mô đun phụ thuộc, mô tả, và thành phần…
    Mã (python):
    1. {
    2.     'name': 'Idea',
    3.     'version': '1.0',
    4.     'author': 'OpenERP',
    5.     'description': 'Ideas management module',
    6.     'category': 'Tools',
    7.     'website': 'http://openerp.com',
    8.     'depends': ['base'],
    9.     'update_xml': [
    10.         'security/groups.xml',
    11.         'security/ir.model.access.csv',
    12.         'workflow/workflow.xml',
    13.         'view/views.xml',
    14.         'wizard/wizard.xml',
    15.         'report/report.xml',
    16.     ],
    17.     'demo_xml': ['demo/demo.xml'],
    18.     'active': False,
    19. }
    (còn tiếp..)
  2. admin Administrator

    Object Service – ORM
    Là thành phần chính của OpenObject, Object Service(OSV) đưa ra một tầng ánh xạ hoàn thiện giữa đối tượng trong framework và bảng dữ liệu quan hệ dưới PostgreSQL, giúp các nhà phát triển khỏi phải viết các câu lệnh SQL dài dòng.
    Các đối tượng được khai báo giống như các lớp trong Python, được kế thừa từ lớp osv.osv; các đối tượng này là một phần của các model trong OpenObject; tồn tại nhờ tầng ORM.
    Để khai báo một đối tượng trong OpenERP, bạn dùng các thuộc tính được định sẵn như sau:
    Ví dụ: khai báo đối tượng idea_idea
    Mã (python):
    1. from osv import osv, fields
    2. class idea(osv.osv):
    3.     _name = 'idea.idea'
    4.     _columns = {
    5.         'name': fields.char('Title', size=64, required=True, translate=True),
    6.         'state': fields.selection([('draft','Draft'),
    7.                         ('confirmed','Confirmed')],'State',required=True,readonly=True),
    8.         # Description is read-only when not draft!
    9.         'description': fields.text('Description', readonly=True,
    10.                         states={'draft': [('readonly', False)]} ),
    11.         'active': fields.boolean('Active'),
    12.         'invent_date': fields.date('Invent date'),
    13.         # by convention, many2one fields end with '_id'
    14.         'inventor_id': fields.many2one('res.partner','Inventor'),
    15.         'inventor_country_id': fields.related('inventor_id','country',
    16.                             readonly=True, type='many2one',
    17.                             relation='res.country', string='Country'),
    18.         # by convention, *2many fields end with '_ids'
    19.         'vote_ids': fields.one2many('idea.vote','idea_id','Votes'),
    20.         'sponsor_ids': fields.many2many('res.partner','idea_sponsor_rel',
    21.                             'idea_id','sponsor_id','Sponsors'),
    22.         'score': fields.float('Score',digits=(2,1)),
    23.         'category_id' = many2one('idea.category', 'Category'),
    24.     }
    25.  
    26.     _defaults = {
    27.         'active': lambda *a: 1, # ideas are active by default
    28.         'state': lambda *a: 'draft', # ideas are in draft state by default
    29.     }
    30.  
    31.     def _check_name(self,cr,uid,ids):
    32.         for idea in self.browse(cr, uid, ids):
    33.             if 'spam' in idea.name: return False # Can't create ideas with spam!
    34.         return True
    35.     _sql_constraints = [('name_uniq','unique(name)', 'Idea must be unique!')]
    36.     _constraints = [(_check_name,'Please avoid spam in ideas !', ['name'])]
    37.  
    38. idea() # Instantiate the class
    39.  
    • _name (bắt buộc)
      • Tên đối tượng, được viết dưới dạng kí pháp dấu chấm '.'; ví dụ: _name = 'idea.idea'
    • _columns (bắt buộc)
      • Một dictionaries có khóa là tên trường và giá trị là kiểu dữ liệu của trường
      • Ví dụ:
        Mã (python):
        1. _columns = {
        2.     'name': fields.char('Title', size=64, required=True, translate=True),
        3.     # 'tên trường': 'kiểu dữ liệu'
        4. }
    • _defaults
      • Một dictionaries có khóa là tên trường và giá trị là các hàm tính toán để trả về giá trị mặc định
      • Ví dụ:
        Mã (python):
        1. _defaults = {
        2.     'active': lambda *a: 1,
        3.     'state': lambda *a: 'draft',
        4.     # 'trường dữ liệu' : 'hàm trả về giá trị mặc định'
        5. }
    • _auto
      • Nếu True (mặc định) thì hệ thống sẽ tự động tạo ra bảng dữ liệu dưới csdl; ngược lại nếu là False thì hệ thống sẽ không tự tạo, nếu muốn thì bạn sẽ tạo các bảng dữ liệu/khung nhìn trong phương thức init()
    • _inherit
      • Tên của đối tượng cha (_name của đối tượng cha cần thừa kế); ví dụ: bạn muốn thừa kế từ đối tượng sale_order có _name là 'sale.order' thì bạn khai báo: _inherit = 'sale.order'
    • _inherits
      • Đa thừa kế, là một dictionaries có khóa là các đối tượng cha muốn thừa kế, giá trị là các tên các trường khóa ngoại tương ứng mà bạn muốn dùng.
      • Ví dụ: _inherits = {'product.template': 'product_tmpl_id'}
    • _constraints
      • Là một list các tuple định nghĩa các ràng buộc dữ liệu viết bằng Python, cú pháp: (func_name, message, fields)
      • Ví dụ: _constraints = [(_check_name,'Please avoid spam in ideas !', ['name'])]
    • _sql_constraints
      • Là một list các tuple định nghĩa các ràng buộc dữ liệu viết bằng SQL, cú pháp: (name, sql_def, message)
      • Ví dụ: _sql_constraints = [('name_uniq','unique(name)', 'Idea must be unique!')]
    • _log_access
      • Nếu True (mặc định), 4 trường dữ liệu (create_uid, create_date, write_uid, write_date) sẽ được tạo ra để ghi log lại các thao tác trên các bản ghi của bảng dữ liệu, bạn có thể dùng hàm perm_read() của osv để truy xuất các trường này.
    • _order
      • Một list các tên trường; hệ thống sẽ sắp xếp thứ tự của các bản ghi theo thứ tự của các tên trường này
      • Ví dụ: _order = 'default_code,name_template'; sẽ sắp xếp thứ tự các bản ghi theo default_code và name_template
    • _rec_name
      • Tên trường dữ liệu được dùng thay cho trường dữ liệu name, được dùng trong hàm name_get() của osv; giá trị mặc định của _rect_name là 'name'
      • Ví dụ: _rec_name = 'ean'
    • _sql
      • Đoạn mã SQL dùng để tạo bảng/khung nhìn cho đối tượng này (nếu _auto là False); hoặc bạn cũng có thể chạy đoạn SQL trong phương thức init()
    • _table
      • Tên bảng dữ liệu (mặc định: các từ trong _name được phân cách bằng dấu chấm '.' sẽ được thay bằng dấu gạch dưới '_'); ví dụ: _name = 'idea.idea' thì hệ thống sẽ tự động tạo ra một bảng dữ liệu có tên 'idea_idea'
    (Bài sau: Kiểu thừa kế và các kiểu dữ liệu, các phương thức orm)
  3. admin Administrator

    Cơ chế thừa kế trong OpenERP
    [IMG]
    • Đơn thừa kế
      • Mở rộng
        • Khai báo:
          • _name = obj1 (ta có 1 object (đối tượng) ban đầu là obj1)
          • _inherit = obj1
        • Dùng để thêm các fields (trường) mới.
        • Object mới này có thể dùng các view (màn hình giao diện) của object obj1
        • Được lưu trữ cùng bảng trong cơ sở dữ liệu
      • Tạo mới
        • Khai báo:
          • _name = 'tên mới'
          • _inherit = obj1
        • Dùng để sao chép các fields của một object nào đó.
        • Object mới này sẽ không dùng được các view của obj1
        • Được lưu thành 2 bảng trong cơ sở dữ liệu
    • Đa thừa kế
      • Khai báo:
        • _name = 'tên mới'
        • _inherits = 'obj1, ...'
      • Thừa kế từ nhiều object
      • Object mới này sẽ không dùng được các view của các object thừa kế
      • Lưu trữ thành 2 bảng dữ liệu khác nhau
      • Khi tạo 1 thể hiện của object mới thì hệ thống sẽ tạo một thể hiện của các object mà bạn chỉ ra trong _inherits;
    thinhgr and openerpvietnam like this.
  4. admin Administrator

    Một số loại trường dữ liệu ORM
    Đối tượng có thể chứa các trường dữ liệu thuộc 3 loại sau: đơn giản, quan hệ, và chức năng. Loại đơn giản là các kiểu dữ liệu như: số nguyên, chấm động, boolean, chuỗi.. Các trường có kiểu dữ liệu quan hệ biểu thị cho các mối quan hệ giữa các đối tượng (one2many, many2one, many2many). Theo mặc định, các trường có kiểu dữ liệu là chức năng sẽ không được lưu trữ trong cơ sở dữ liệu mà được tính toán lại mỗi lần chạy.

    Các thuộc tính có thể có trong tất cả các trường dữ liệu:
    • string: nhãn của trường (bắt buộc phải có)
    • required: nếu là True thì bắt buộc người dùng phải nhập dữ liệu vào trường này
    • readonly: nếu là True thì người dùng không thể thay đổi dữ liệu trường này.
    • help: giải thích, hướng dẫn cho trường dữ liệu này
    • select: 1 để đưa trường này vào màn hình tìm kiếm và để giúp tối ưu cho việc lọc danh sách (được đánh dấu index trong csdl)
    • context: là một dictionaries chứa các tham số về ngữ cảnh như: ngôn ngữ sử dụng là gì, múi giờ là gì, model nào đang đang được sử dụng, id là bao nhiêu..
      • Ví dụ: {‘lang’: ‘en_us’, ‘tz’: ‘UTC’, …}
    • change_default: nếu là True thì trường này có thể sử dụng để làm điều kiện để thay đổi các giá trị mặc định của một đối tượng.
    • states: thay đổi thuộc tính của trường này dựa vào trường state.
      • Ex: (→42,46)
        Mã (python):
        1. 'state': fields.selection([('draft','Draft'),
        2.     ('confirmed','Confirmed')],'State',required=True,readonly=True),
        3. 'description': fields.text('Description', readonly=True,
        4.         states={'draft': [('readonly', False)]} ),
    Những field đơn giản
    • boolean(...)
    • integer(...)
    • date(...)
    • datetime(...)
    • time(...)
    • Ví dụ:
      Mã (python):
      1. 'active': fields.boolean('Active'),
      2. 'priority': fields.integer('Priority'),
      3. 'start_date': fields.date('Start Date'),
    • Các trường dạng kí tự, văn bản
      • char(string,size,translate=False,..)
      • text(string, translate=False, …)
        • translate: nếu là True thì người dùng có thể dịch giá trị của trường này (dữ liệu người dùng nhập vào) sang một ngôn ngữ khác.
    • float(string, digits=None, …): Giá trị số thập phân được định rõ bởi số con số phía trước và sau dấu thập phân.
      • digits: tuple (precision, scale) (→57)
        Mã (python):
        1. 'score': fields.float('Score',digits=(2,1)),
        2.  
      • Nếu không chỉ ra giá trị của digits thì trường này có kiểu float, không phải là decimal.
    • selection(values, string, ...) : Trường dữ liệu kiểu selection cho phép người dùng chọn một giá trị trong một tập các giá trị có sẵn.
      • values: là một list chứa các tuple có giá trị ('khóa', 'giá trị') hoặc là một hàm trả về 1 list như trên; 1 trường dữ liệu kiểu selection bắt buộc phải khai báo giá trị của values.
    • binary(string, filters=None, ...) : Chứa nội dung nhị phân của các tập tin
      • filters: bộ lọc các tập tin.
      • Ví dụ:
        Mã (python):
        1.  'picture': fields.binary('Picture', filters='*.png,*.gif')
    • reference(string, selection, size,..): Các trường có kiểu này có thể liên kết tới các đối tượng khác; bạn có thể chọn một giá trị của đối tượng bạn muốn nhờ vào một widget được OpenERP cung cấp.
      • selection: chứa 1 list các tuple gồm tên (_name) các đối tượng muốn liên kết và nhãn của các đối tượng đó. Khi dùng 1 trường kiểu reference thì selection bắt buộc phải có.
      • size: số kí tự tối đa được lưu xuống csdl; theo định dạng: 'model_name,object_id'
      • Ví dụ:
        Mã (python):
        1. 'contact': fields.reference('Contact',[ res.partner','Partner'), res.partner.contact','Contact')], 128)
    Những field quan hệ
    • Các thuộc tính được dùng trong các trường dữ liệu kiểu quan hệ
      • domain: dùng để lọc dữ liệu theo một điều kiện nào đó (xem thêm hàm search())
    • many2one(obj, ondelete='set null', …) (→50)
      Mã (python):
      1. 'inventor_id': fields.many2one('res.partner','Inventor'),
      • Quan hệ tới một đối tượng khác (khóa ngoại)
      • obj: tên (_name) của đối tượng liên kết (bắt buộc phải có)
        ondelete: cho hệ thống biết sẽ làm gì khi xóa dữ liệu trong bảng dữ liệu của đối tượng mà trường này liên kết tới, e.g. 'set null', 'cascade', xem thêm tài liệu PostgreSQL.
    • one2many(obj, field_id, …) (→55)
      Mã (python):
      1. 'vote_ids': fields.one2many('idea.vote','idea_id','Votes'),
      • Quan hệ một nhiều tới đối tượng khác (ngược với many2one)
      • obj: tên (_name) của đối tượng liên kết (bắt buộc phải có)
      • field_id: tên trường kiểu many2one; liên kết với đối tượng chứa trường đang khai báo này; bắt buộc phải có.
    • many2many(obj, rel, field1, field2, …) (→56)
      Mã (python):
      1. 'sponsor_ids': fields.many2many('res.partner','idea_sponsor_rel', 'idea_id','sponsor_id','Sponsors'),
      • Quan hệ nhiều nhiều giữa các đối tượng
      • obj: tên (_name) của đối tượng liên kết (bắt buộc phải có)
      • rel: bảng dữ liệu quan hệ trung gian giữa 2 đối tượng
      • field1: tên của cột trong bảng dữ liệu; chứa id của đối tượng hiện tại (bắt buộc phải có)
      • field2: tên của cột trong bảng dữ liệu; chứa id của đối tượng liên kết (bắt buộc phải có)
    Những field chức năng
    function(fnct, arg=None, fnct_inv=None, fnct_inv_arg=None, type='float', fnct_search=None, obj=None, method=False, store=False, multi=False,…)
    Các trường dữ liệu kiểu chức năng được xem như là một trường bình thường; nhưng chủ yếu là được dùng để tính toán hơn là lưu trữ.
    • fnct: hàm tính toán giá trị của trường (bắt buộc phải có); được khai báo như sau:
      • def fnct(self, cr, uid, ids, field_name, arg, context)
      • trả về 1 dictionaries dạng { ids→values }; values là các giá trị kiểu giống như kiểu khai báo trong trường.
    • fnct_inv: hàm này sẽ được gọi khi ghi giá trị của field xuống csdl; khai báo như sau:
      • def fnct_inv(obj, cr, uid, id, name, value, fnct_inv_arg, context)
    • type: kiểu của giá trị trả về (không được trả về kiểu function)
    • nct_search: hàm tìm kiếm của field này, sẽ hữu ích trong trường hợp bạn đưa ‘field_function_name’ lên chỗ ô tìm kiếm.
      • def fnct_search(obj, cr, uid, obj, name, args)
      • Hàm này sẽ trả về 1 list các tuple để làm đối số cho hàm search(); ví dụ: [('id','in',[1,3,5])]
    • obj: tên (_name) của đối tượng
    • store, multi: để tối ưu hóa hệ thống(xem cách dùng trong phần Performance);store=True sẽ cho phép lưu kết quả tính toán xuống csdl; chỉ tính toán lại khi có sự thay đổi đến một số trường nào đó; multi để gộp các trường kiểu function có giá trị multi giống nhau để tính toán một lần;
    • related(f1, f2, …, type='float', …) trường dữ liệu kiểu này được dùng để lấy các thông tin liên quan của các trường liên kết khác.
      • f1,f2,...: các trường liên kết tới trường cần lấy thông tin(bắt buộc có f1) (→51)
        Mã (python):
        1. 'inventor_country_id': fields.related('inventor_id','country', readonly=True, type='many2one', relation='res.country', string='Country'),
      • type: kiểu dữ liệu của trường cần lấy thông tin
    • property(obj, type='float', view_load=None, group_name=None, …)
      • Các trường kiểu property giúp bạn lấy được các giá trị tương ứng với quyền truy cập.
      • obj: tên đối tượng (bắt buộc)
      • type: kiểu dữ liệu của trường tương ứng
    Gợi ý: những trường dữ liệu kiểu quan hệ có tính chất đối xứng như sau:
    one2many ↔ many2one đối xứng
    many2many ↔ many2many đối xứng khi nghịch đảo (chuyển đổi giữa field1 và field2)
    one2many ↔ many2one + many2one ↔ one2many = many2many

    Một số tên trường đặc biệt / dành riêng của OpenERP
    Một số tên trường được dành riêng cho OpenObject; trong đó có một số tên trường được hệ thống tự động tạo ra; khi đó các tên trường mà bạn đặt có tên trùng với những trường này sẽ bị bỏ qua.
    • id
      • id của các bản ghi của mỗi đối tượng (ORM tự động tạo; nên bạn không cần thêm trường này vào)
    • name
      • Mặc định, hệ thống sẽ hiển thị giá trị của trường name này khi hiển thị đối tượng chứa trường này trên màn hình. Nếu không có trường name này thì bạn phải thiết lập rec_name để chỉ ra một trường khác để hệ thống lấy giá trị của trường đó.
    • active
      • Một bản ghi có thuộc tính active là False thì sẽ bị ẩn đi; bạn sẽ không thấy nó trong màn hình danh sách trong OpenERP
    • sequence
      • Chỉ ra thứ tự sắp xếp của các bản ghi; trong màn hình danh sách bạn có thể kéo thả để sắp xếp lại các thứ tự này.
    • state
      • Chỉ ra từng giai đoạn của một đối tượng; thường được dùng cho workflow
    • parent_id
      • Chỉ ra cấu trúc cây của các bản ghi; bạn có thể dùng toán tử child_of để tìm các bản ghi con.
    • parent_left, parent_right
      • Được dùng kết hợp với trường _parent_store có trong mỗi đối tượng; giúp cho việc truy xuất dữ liệu nhanh hơn (xem thêm phần Performance Optimization)
    • create_date, create_uid, write_date, write_uid
      • Được sử dụng để ghi log lại: ai là người tạo ra bản ghi; người cập nhật cuối là ai; ngày tạo và ngày cập nhật cuối các bản ghi. Nếu log_access = False thì các trường này không được tạo ra. (ORM sẽ tự động tạo ra các trường này, do đó bạn không phải tạo chúng)
  5. admin Administrator

    Làm việc với ORM

    Vì các đối tượng trong OpenERP đều được thừa kế từ lớp osv.osv nên các đối tượng này đều thừa kế tất cả các phương thức ORM. Bạn có thể gọi đến các phương thức này ngay bên trong đối tượng; hoặc bạn có thể tạo ra một thể hiện của đối tượng, rồi sau đó gọi đến các phương thức này từ các thể hiện đó.

    Các phương thức ORM của đối tượng osv.osv
    • Cách truy cấp đến đối tượng
      • self.pool.get('object_name') dùng để lấy một đối tượng nào đó có tên 'object_name'
    • Các tham số thường dùng trong các phương thức ORM
      • cr: kết nối cơ sở dữ liệu (con trỏ csdl)
      • uid: id của người dùng đang thao tác
      • id: là một list chứa các id của các bản ghi; hoặc là 1 số nguyên duy nhất khi chỉ có một id.
      • context: là một dictionaries chứa các tham số về ngữ cảnh như: ngôn ngữ sử dụng là gì, múi giờ là gì, model nào đang đang được sử dụng, id là bao nhiêu.. Ví dụ:
        {'lang': 'en_US', ... }
    • create(cr, uid, values, context=None)
      • Tạo ra một bản ghi mới với giá trị được truyền vào.
      • Trả về: id của các bản ghi mới
      • values: là một dictionaries với khóa là tên trường, giá trị là giá trị của trường đó.
      • Mã (python):
        1. idea_id = self.create(cr, uid, { 'name': 'Spam recipe', 'description' : 'spam & eggs', 'inventor_id': 45,})
    • search(cr, uid, args, offset=0, limit=None, order=None, context=None, count=False)
      • Trả về: danh sách các id của các bản ghi phù hợp với các tiêu chí tìm kiếm
      • args: một list các tuple chứa các tiêu chí tìm kiếm
      • offset: bắt đầu lấy các bản ghi từ vị trí nào
      • limit: số lượng các bản ghi trả về
      • order: sắp xếp thứ tự các bản ghi theo trường nào(mặc định: self._order)
      • count: nếu True, trả về số bản ghi khớp với điều kiện tìm kiếm; không phải là id của các bản ghi.
      • #Các toán tử: =, !=, >, >=, <, <=, like, ilike, #in, not in, child_of, parent_left, parent_right
      • #Các toán tử kết hợp: '&' (mặc định), '|', '!'
        Mã (text):
        1. # Tìm kiếm những đối tác có tên khác 'spam' hoặc có giá trị partner_id khác 34
        2. ids = self.search(cr, uid, [ '|', ('partner_id', '!=', 34), '!', ('name', 'ilike', 'spam'), ],  order='partner_id')
    • read(cr, user, ids, fields=None, context=None)
      • Trả về: một list các dictionaries với khóa là tên trường, và giá trị là giá trị của các trường cần lấy
      • fields: một list tên các trường dữ liệu cần lấy (mặc định: lấy tất cả các trường dữ liệu)
      • Mã (python):
        1. results = self.read(cr, uid, [42,43], ['name', 'inventor_id'])
        2. print 'Inventor:', results[0]['inventor_id']
    • write(cr, uid, ids, values, context=None)
      • Cập nhật thông tin cho các bản ghi có id trong list ids
      • Trả về: True
      • values: một dictionaries với khóa là tên trường cần cập nhật thông tin, và giá trị là các giá trị mới cần cập nhật.
      • Mã (python):
        1. self.write(cr, uid, [42,43], { 'name': 'spam & eggs', 'partner_id': 24,})
    • copy(cr, uid, id, defaults,context=None)
      • Tạo bản sao của một bản ghi; một số trường sẽ lấy giá trị mặc định mà ta đã định sẵn.
      • Trả về: True
      • defaults: là một dictionaries với khóa là tên trường và giá trị là giá trị mặc định. Sau khi copy thì hầu như các trường dữ liệu sẽ có giá trị giống như bản cũ, chỉ khác ở các trường mà ta đã chỉ ra trong defaults
    • unlink(cr, uid, ids, context=None)
      • Xóa các bản ghi có id nằm trong list ids
      • Trả về: True
      • Mã (python):
        1. self.unlink(cr, uid, [42,43])
    • browse(cr, uid, ids, context=None)
      • Trả về các bản ghi ở dạng đối tượng hoặc một list các đối tượng; cho phép truy xuất tới từng trường hoặc các quan hệ của đối tượng bằng cách dùng kí pháp dấu '.'
      • Trả về: đối tượng hoặc một list các đối tượng mà ta muốn

    • Mã (python):
      1. idea = self.browse(cr, uid, 42)
      2. print 'Idea description:', idea.description
      3. print 'Inventor country code:', idea.inventor_id.address[0].country_id.code
      4. for vote in idea.vote_ids:
      5.     print 'Vote %2.2f' % vote.vote
    • default_get(cr, uid, fields, context=None)
      • Trả về: một dictionaries gồm khóa là tên trường và giá trị là giá trị mặc định của trường đó. (các giá trị mặc định này được thiết lập trong khi tạo đối tượng hoặc do người dùng thiết lập khi sử dụng hoặc thông qua các biến context)
      • fields: là một list chứa các tên trường
      • Mã (python):
        1. defs = self.default_get(cr,uid, ['name','active'])
        2. # active should be True by default
        3. assert defs['active']
    • log(cr, uid, id, details=True)
      • Trả về: id của bản ghi được tạo ra trong res.log
      • message: nội dung
      • context: if passed then context is stored in db
      • Mã (python):
        1. self.log(cr, uid, st.id, _('Statement %s is confirmed, journal items are created.') % (st_number,))
    • perm_read(cr, uid, ids, details=True)
      • Trả về: một list các dictionaries các thông tin sở hữu, chỉnh sửa các bản ghi mà bạn muốn.
      • details: nếu details là True thì *_uid sẽ được thay thế bởi tên của người dùng.
      • Dictionaries được trả về có khóa là các *_uid và giá trị là giá trị của các trường tương ứng; id của đối tượng là bao nhiêu, người tạo là ai, ngày tạo, người cập nhật cuối cùng là ai, thời gian cập nhật..

    • Mã (python):
      1. perms = self.perm_read(cr,uid,[42,43])
      2. print 'creator:', perms[0].get('create_uid', 'n/a')
    • fields_get(cr, uid, fields=None, context=None)
      • Trả về một dictionaries gồm các dictionaries chứa các thông tin mô tả về trường dữ liệu mà bạn muốn xem.
      • fields: là 1 list các tên trường mà bạn muốn xem
      • Mã (python):
        1. class idea idea(osv.osv):
        2. (...)
        3. _columns = {
        4.     'name' : fields.char('Name',size=64)
        5. (...)
        6. def test_fields_get(self,cr,uid):
        7.     assert(self.fields_get('name')['size'] == 64)
    • fields_view_get(cr, uid, view_id=None, view_type='form', context=None, toolbar=False)
      • Trả về một dictionaries chứa thông tin mô tả các thành phần của một giao diện màn hình (view) mà bạn muốn (gồm cả các view được thừa kế và mở rộng)
      • view_id: giá trị id của màn hình giao diện (view) hoặc None
      • view_type: loại màn hình được trả về là biểu mẫu hay danh sách khi view_id = None
      • toolbar: nếu là True thì thanh công cụ chứa các nút bấm làm một hành động gì đó bên tay phải sẽ được hiển thị
      • Mã (python):
        1. def test_fields_view_get(self,cr,uid):
        2.     idea_obj = self.pool.get('idea.idea')
        3.     form_view = idea_obj.fields_view_get(cr,uid)
    • name_get(cr, uid, ids, context={})
      • Trả về bộ tuples with the text representation các đối tượng được yêu cầu bởi quan hệ to-many
      • Trả về một list các tuple chứa các tên của các đối tượng có kiểu quan hệ.
      • Mã (python):
        1. # Ideas should be shown with invention date
        2. def name_get(self,cr,uid,ids):
        3.     res = []
        4.     for r in self.read(cr,uid,ids['name','create_date'])
        5.         res.append((r['id'], '%s (%s)' (r['name'],year))
        6.     return res
    • name_search(cr, uid, name='', args=None, operator='ilike', context=None, limit=80)
      • Trả về một list các tuple chứa tên các đối tượng quan hệ phù hợp với các điều kiện tìm kiếm.
      • Trả về danh sách tên các đối tượng phù hợp với các tiêu chí, được sử dụng để cấp cho các mối quan hệ to-many. Tương đương với việc dùng hàm search() để tìm theo tên; rồi dùng hàm name_get() để lấy kết quả trả về.
      • name: tên đối tượng tìm kiếm
      • operator: các toán tử để tìm kiếm
      • args, limit: giống với search())
      • Mã (python):
        1. # Countries can be searched by code or name
        2. def name_search(self,cr,uid,name='',
        3.         args=[],operator='ilike',context={},
        4.         limit=80):
        5.     ids = []
        6.     if name and len(name) == 2:
        7.         ids = self.search(cr, user,
        8.               [('code', '=', name)] + args,
        9.               limit=limit, context=context)
        10.     if not ids:
        11.         ids = self.search(cr, user,
        12.             [('name', operator, name)] + args,
        13.             limit=limit, context=context)
        14.     return self.name_get(cr,uid,ids)
    • export_data(cr, uid, ids, fields, context=None)
      • Xuất dữ liệu của các trường trong những đối tượng mà bạn muốn; hệ thống sẽ trả về một dictionaries chứa dữ liệu của các trường theo dạng ma trận. Người dùng có thể dùng tính năng xuất dữ liệu thông qua các menu tương ứng.
      • fields: list các tên trường cần xuất ra
      • context có thể chứa thuộc tính import_comp (mặc định: False) để dữ liệu xuất ra phù hợp với hàm import_data() (phục vụ cho việc nhập dữ liệu lại sau này; và trong quá trình này hệ thống có thể không cho xuất dữ liệu của một số trường)
    • import_data(cr, uid, fields, data, mode='init', current_module='', noupdate=False, context=None, filename=None)
      • Nhập dữ liệu có sẵn vào cho một mô đun bạn muốn; người dùng có thể dùng tính năng nhập dữ liệu này thông qua các menu tương ứng.
      • fields: list tên các trường dữ liệu muốn nhập
      • data: dữ liệu cần nhập vào (xem export_data())
      • mode: 'init' hoặc 'update'; cho hệ thống biết khi nhập vào thì dữ liệu này sẽ được tạo mới hay là cập nhật lại dữ liệu cũ có sẵn.
      • current_module: tên mô đun
      • noupdate: không cho cập nhật lại dữ liệu khi nhập
      • filename: tập tin chứa các thông tin tình trạng nhập dữ liệu để giúp cho việc phục hồi nếu nhập sai
    Mẹo: khi gọi các hàm thông qua webservice thì bạn nên dùng hàm read(); ngược lại nên dùng hàm browse()

    (Tiếp theo: Tạo view, action, menu ..)
  6. Đức Minh Trần New Member

    cuối năm bận rộn thế mà a admin vẫn bỏ công ra dịch bài. Những kiến thức này rất bổ ích,
    Thanks a nhìu nhé ^^
  7. admin Administrator

    Xây dựng giao diện cho mô đun
    Một phần trong việc xây dựng mô đun là ta phải xây dựng giao diện; một số thành phần cơ bản của giao diện là: menus (trình đơn), views (màn hình giao diện), actions (hành động, tác vụ), roles (vai trò), access rights (quyền truy cập) …
    Cấu trúc XML phổ biến
    Các tập tin XML được khai báo trong thuộc tính update_xml của mô đun chứa các đoạn mã để xây dựng giao diện theo như mẫu sau:
    [IMG]

    Mỗi một loại bản ghi (view, menu, action) hỗ trợ một số các thuộc tính và thực thể con; nhưng tất cả chúng đều có chung một số thuộc tính đặc biệt sau đây:

    • id: Số id duy nhất của bản ghi (xml_id) trong mô đun
    • ref: được dùng để tham chiếu tới một bản ghi khác (để tham chiếu tới các bản ghi nằm ở các mô đun khác thì bạn phải thêm tên mô đun chứa bản ghi đó vào phía trước tham chiếu; ví dụ: sale.menu_sale_id)
    • eval: Dùng để tính toán giá trị của một biểu thức Python rồi gán giá trị đó cho phần tử này; bạn cũng có thể sử dụng phương thức ref() để tìm id cơ sở dữ liệu của một xml_id đã có nào đó.
    Gợi ý : kiểm tra XML với XML RelaxNG
    • OpenObject kiểm tra cú pháp và cấu trúc của tập tin XML dựa vào RelaxNG, bạn có thể tìm thấy tập tin này tại thư mục server/bin/import_xml.rng.
    • Nếu bạn muốn kiểm tra thủ công thì có thể dùng công cụ xmllint với cú pháp: xmllint – relaxng /path/to/import_xml.rng <file>


    Cú pháp CSV phổ biến
    Các tập tin CSV có thể được thêm vào trong thuộc tính update_xml, và các bản ghi trong file csv này sẽ được chèn vào cơ sở dữ liệu bằng phương thức import_data() của OSV, để import cho object nào thì bạn chỉ cần đặt tên file csv là tên của object (ví dụ: sale.order.csv). ORM sẽ tự động kết nối lại các mối quan hệ dựa trên các tên cột đặc biệt sau đây:
    [IMG]
    [IMG]

    Menus và actions
    Actions được khai báo như bản ghi thông thường và có thể được kích hoạt trong 3 cách sau:
    • Khi nhấp vào menu, mà menu này có liên kết tới một action nào đó
    • Khi nhấn vào một button, mà button này có liên kết tới một action nào đó
    • Khi nhấn vào các action ngữ cảnh (thường nằm bên phải trong màn hình form view) của object
    Khai báo action
    [IMG]

    • id
      • id của action trong bảng dữ liệu ir.actions.act_window, phải là duy nhất
    • name
      • tên của action (bắt buộc phải có)
    • view_id
      • màn hình giao diện (views) cần hiển thị (nếu không chỉ ra thì hệ thống sẽ tự động lấy view có độ ưu tiên cao nhất của loại views được chỉ ra ở view_type)
    • domain
      • là một tuple chứa các điều kiện để lọc bớt nội dung (xem các đối số của phương thức search())
    • context
      • một dictionary chứa các thông tin như múi giờ là gì, ngôn ngữ đang sử dụng, id của menu đang dùng.. để truyền vào cho view.
    • res_model
      • chỉ ra đối tượng mà view được định nghĩa trên đối tượng đó
    • view_type
      • nếu giá trị là form thì hệ thống sẽ hiển thị bản ghi ở chế độ chỉnh sửa, còn nếu là tree thì hệ thống sẽ hiển thị ra dạng cây chỉ để xem
    • view_mode
      • nếu view_type là form thì bạn có thể dùng view_mode để cho hệ thống biết bạn muốn hiển thị dữ liệu theo dạng form, tree hay calendar hoặc một kiểu nào đó khác ...
    • target
      • nếu giá trị là new thì hệ thống sẽ mở màn hình giao diện trong một cửa sổ mới
    • search_view_id
      • id của search view mà bạn muốn thay cho search view mặc định (từ phiên bản 5.2 trở đi)
    Khai báo menu

    Mỗi mục menu là một bản ghi trong bảng dữ liệu ir.ui.menu; và mỗi menu được kết nối với một action tương ứng thông qua một bản ghi trong bảng dữ liệu ir.model.data
    [IMG]

    • id
      • id của menu; phải là duy nhất
    • parent
      • id của menu cha trong hệ thống phân cấp
    • name
      • Nhãn của menu (tùy chọn) (mặc định hệ thống sẽ lấy name của action)
    • action
      • id của action mà menu này liên kết tới (nếu có)
    • icon
      • biểu tượng của menu (ví dụ như terp-graph, STOCK_OPEN, xem thêm tại doc.opernerp.com)
    • groups
      • danh sách các nhóm người dùng có thể thấy menu này (nếu không chỉ ra thì tất cả các nhóm đều có thể thấy)
    • sequence
      • con số để sắp thứ tự của các menu; menu nào có số thứ tự nhỏ hơn sẽ được sắp trước.



    Views và thừa kế
    View được chia theo cấu trúc phân cấp. Bạn có thể khai báo nhiều view cùng kiểu cho một object, và hệ thống sẽ tự động lấy view nào có độ ưu tiên cao hơn để hiển thị. Để thêm hoặc bớt một số trường trên view, bạn có thể dùng thừa kế view.
    [IMG]

    • id
      • id của view (duy nhất)
    • name
      • Tên view
    • model
      • object mà ta muốn tạo view (giống như res_model trong action)
    • type
      • Loại view: form, tree, graph, calendar, search, gantt (search mới có trong 5.2)
    • priority
      • Mức độ ưu tiên của view, con số càng nhỏ thì độ ưu tiên càng cao (mặc định là 16)
    • arch
      • Cấu trúc của view, xem thêm những loại view khác nhau bên dưới


    Forms (để xem / chỉnh sửa các bản ghi)

    Các form cho phép tạo / chỉnh sửa một tài nguyên nào đó, và tương ứng với phần tử <form> khi khai báo một view.
    [IMG]
    [IMG]

    Các phần tử của Form
    Thuộc tính chung cho tất cả các phần tử:

    • string: nhãn của phần tử
    • nolabel: 1 để ẩn nhãn của field
    • colspan: số cột mà field sẽ chiếm để hiển thị
    • rowspan: số hàng mà field sẽ chiếm để hiển thị
    • col: số cột mà field phải phân bổ cho các phần tử con của nó
    • invisible: 1 để ẩn phần tử này đi; không cho hiển thị trên view
    • eval: nội dung sẽ chứa một biểu thức Python; hệ thống sẽ thực hiện đoạn code này rồi trả giá trị về cho phần tử chứa nó
    • attrs: hệ thống sẽ xét giá trị của các thuộc tính readonly, invisible, required dựa vào giá trị của các field khác
    Các phần tử:
    • field
      • hệ thống sẽ tự động hiển thị các widget của một field dựa vào kiểu dữ liệu của field đó. Một field sẽ có các thuộc tính như sau:
        • string: nhãn của field (mặc định là nhãn này sẽ lấy giá trị name của field)
        • select: nếu là 1 thì hệ thống sẽ tự động đưa thêm field này vào trong chỗ các ô tìm kiếm bình thường nằm phía trên list view; 2 thì sẽ đưa vào chỗ các ô tìm kiếm nâng cao
        • nolabel: nếu là 1 thì hệ thống sẽ ẩn nhãn của field này
        • required: bắt buộc nhập dữ liệu hay không? hệ thống sẽ ghi đè thuộc tính required được định nghĩa trong file python bằng giá trị được chỉ ra ở đây
        • readonly: dữ liệu có phải chỉ được xem hay không? hệ thống sẽ ghi đè thuộc tính readonly được định nghĩa trong file python bằng giá trị được chỉ ra ở đây
        • password: True để ẩn các ký tự được nhập vào trong field này
        • context: là một biến ngữ cảnh (một dictionary chứa các thông tin về múi giờ, ngôn ngữ ..)
        • domain: là một list các tuple để lọc bớt các giá trị
        • on_change: kích hoạt một phương thức Python khi giá trị của field này bị thay đổi
        • groups: danh sách các nhóm (id của nhóm) được phép thấy field này; được phân cách bằng dấu phẩy
        • widget: chọn widget để hiển thị (one2many_list, many2many, url, email, image, float_time, reference, text_wiki, text_html, progressbar)
    • properties
      • một widget động hiển thị tất cả các thuộc tính có sẵn
    • button
      • Một widget dùng để nhấn chuột, được liên kết với các action. Những thuộc tính cụ thể của một button:
        • type: button có các loại như sau : workflow (mặc định), object, hoặc action
        • name: có thể là một signal (tín hiệu) trong workflow, tên hàm (không có dấu ngoặc) hoặc một action nào đó (tùy theo type ở phía trên)
        • confirm: thông điệp khi người dùng nhấn vào button này
        • states: nút này sẽ được hiển thị khi trạng thái của object thuộc danh sách các trạng thái được phân cách bằng dấu phẩy
        • icon: biểu tượng của button (tùy chọn) (bạn có thể chọn một trong các biểu tượng GTK STOCK; ví dụ như: gtk-ok)
    • separator
      • Dòng kẻ ngang để phân cách các phần tử được hiển thị trên view; dòng này có thể có nhãn nằm phía trên
    • newline
      • hiển thị xuống dòng
    • label
      • hiển thị một dòng tiêu đề nào đó
    • group
      • Dùng để sắp xếp các field thành các nhóm; bạn có thể đặt nhãn cho nhóm
    • notebook, page
      • Phần tử notebook là một phần tử chứa các tab nhỏ được tạo từ các phần tử page. Có các thuộc tính:
        • name: nhãn của tab/page
        • position: vị trí tabs trong notebook (trong, trên, dưới, trái, phải)
    Views động
    Ngoài những gì có thể thực hiện được với thuộc tính states và attrs, thì các phần tử trên view có thể gọi đến các hàm (thông qua các button có kiểu là object hoặc thuộc tính on_change) để thực hiện một số hành động nào đó. Các hàm có thể thay đổi giao diện màn hình của view bằng cách trả về một dictionary như sau:

    • value
      • một dictionary có khóa là tên của các trường, giá trị là giá trị đã được cập nhật của trường đó. ví dụ: return {'value': {'order_id': 1, 'name': 'abc'}}
    • domain
      • dictionary có khóa là tên của các trường, giá trị là các điều kiện lọc đã được cập nhật. Ví dụ: return { 'domain': {'product_uom': [], product_uos': []}}
    • warning
      • dictionary chứa các khóa: title, message; giá trị tương ứng là tiêu đề cần hiển thị, và nội dung cần thông báo.
        Ví dụ: return {'warning': {'title': 'Warning!', 'message': 'This is a warning!'}


    Màn hình danh sách (Lists / Tree)
    Màn hình danh sách bao gồm các phần tử field, được tạo ra với kiểu là tree, và có một phần tử cha là <tree>.
    • Các thuộc tính
      • colors: một list các màu sắc để hiển thị dữ liệu dựa vào các điều kiện mà ta chỉ ra
      • editable: cho phép chỉnh sửa dữ liệu trực tiếp trên view; bao gồm 2 giá trị: top hoặc bottom
      • toolbar: nếu là True thì hệ thống sẽ hiển thị các thể hiện cao nhất của các đối tượng phân cấp trong toolbar bên cạnh (ví dụ: menu)
    • Những phần tử được chấp nhận
      • field, group, separator, tree, button, filter, newline
    [IMG]

    Calendars
    View này dùng để hiển thị các field có kiểu date ở dạng lịch(phần tử cha là <calendar>)
    • Các thuộc tính
      • color: tên field để tô màu
      • date_start: tên field chứa sự kiện bắt đầu ngày/giờ
      • day_length: Độ dài ngày trên lịch tính theo giờ (Mặc định là 8)
      • date_stop: tên field chứa sự kiện kết thúc ngày/giờ
      • date_delay: tên field chứa thời lượng của sự kiện
    • Những phần tử được chấp nhận
      • field: định nghĩa nhãn cho mỗi sự kiện trên lịch
    [IMG]

    Biểu đồ Gantt
    Biểu đồ dạng thanh thường được dùng để hiển thị thời gian biểu của dự án (phần tử cha <gantt>)
    • Các thuộc tính
      • Giống calendar
    • Những phần tử được chấp nhận
      • field, level
      • phần tử level dùng để định nghĩa cấp độ của biểu đồ Gantt, với field đi kèm được dùng như nhãn để đi vào các cấp độ chi tiết hơn.
    [IMG]

    Biểu đồ
    Dùng để hiển thị các biểu đồ thống kê (phần tử cha <graph>)

    • Các thuộc tính
      • type: loại biểu đồ: cột, tròn (mặc định)
      • orientation: đứng hoặc nằm
    • Những phần tử được chấp nhận
      • field, với những thuộc tính cụ thể sau:
        • field đầu tiên trên màn hình tương đương trục X, thứ 2 là trục Y, thứ 3 là trục Z
        • 2 fields trên trục X, Y thì bắt buộc phải có, field thứ 3 là tùy chọn
        • thuộc tính group được định nghĩa trong field GROUP BY (đặt là 1)
        • Thuộc tính operator đưa ra các phép toán (+,*,**,min,max)

    [IMG]

    Màn hình tìm kiếm
    View tìm kiếm được dùng để tùy chỉnh các bảng điều khiển tìm kiếm ở trên đầu trang của màn hình danh sách, và được khai báo có kiểu là search, có phần tử cha là <search>. Sau khi định nghĩa một search view, gán id duy nhất cho nó; ta thêm nó vào trong action để mở một list view bằng cách dùng field search_view_id trong khai báo của action.
    • Những phần tử được chấp nhận
      • field, group, separator, label, search, filter, newline, properties
        • filter cho phép tạo ra các button để lọc bớt dữ liệu với các điều kiện định trước
        • Thêm thuộc tính context vào trong field để thay đổi ngữ cảnh tìm kiếm
    [IMG]
    [IMG]

    Kế thừa view
    Để chỉnh sửa một view đã có sẵn, thay vì chỉnh sửa trực tiếp, ta sẽ dùng thừa kế view để làm.
    Để tham chiếu và kế thừa từ một view, ta dùng field inherit_id để chỉ đến view cần thừa kế; rồi sau đó bạn có thể thêm, bớt các phần tử bằng cách dùng các biểu thức Xpath; để chỉ ra vị trí cần thêm, bớt bạn dùng thuộc tính position.
    Mẹo: tham khảo thêm về XPath tại www.w3.org/TR/xpath
    • position
      • inside: đặt các phần tử mới vào trong phần tử mà bạn đã dùng xpath để chỉ ra (mặc định)
      • replace: thay thế
      • before: đặt trước
      • after: đặt sau
    • attributes
      • dùng thẻ attribute để thêm thuộc tính mới
    [IMG]
    (Ghi rõ nguồn gốc khi bạn copy, trích dẫn từ terp.vn)
  8. admin Administrator

    Reports (báo cáo)
    Trong OpenERP có một số engine report, để tạo ra các report từ các nguồn khác nhau và xuất ra nhiều định dạng.
    [IMG]
    Các biểu thức đặc biệt được dùng trong các mẫu report sinh ra dữ liệu động và/hoặc bổ sung cấu trúc report vào lúc kết xuất ra.
    Bạn có thể viết thêm các parser (trình phân tích) report để hỗ trợ cho các biểu thức của riêng bạn.
    Các định dạng của report (xem thêm tại doc.openerp.com)
    • sxw2rml
      • Dùng công cụ sxw để chuyển các mẫu (.sxw) của OpenOffice 1.0 thành RML, rồi dùng RML kết xuất ra HTML hoặc PDF
    • rml
      • Các mẫu RML có thể kết xuất trực tiếp thành HTML hoặc PDF
    • xml,xsl:rml
      • Dữ liệu XML + các stylesheet XSL:RML để sinh ra RML
    • odt2odt
      • Dùng các mẫu *.odt của OpenOffice, để sinh ra các tài liệu, chứng từ định dạng *.odt (trong OpenERP 5.2)
    • mako
      • Dùng thư viện mẫu mako để sinh ra HTML, bằng cách nhúng các đoạn mã Python và biểu thức OpenERP vào trong một file text bất kì (trong OpenERP 5.2)
    Các biểu thức được dùng trong các mẫu report OpenERP
    [[ <nội dung> ]] nội dung phía trong 2 dấu ngoặc vuông được xem như là một biểu thức Python dựa vào những biểu thức dưới đây:
    Các biểu thức được định nghĩa trước:
    • objects: chứa một list các bản ghi để in ra
    • data: biến data được lấy từ wizard khởi động report này
    • user: chứa thông tin người dùng hiện tại
    • time: cấp quyền truy cập đến mô đun time của Python
    • repeatIn(list,'var’): biến var sẽ lặp để lấy từng giá trị trong list
    • setTag('tag1','tag2') : thay thế phần tử cha của tag1 trong RML bằng tag2
    • removeParentNode('tag'): xóa phần tử cha của tag trong RML
    • formatLang(value, digits=2, date=False, date_time=False, grouping=True, monetary=False): dùng để định dạng ngày, giờ, các số thập phân
    • setLang('lang_code'): thiết lập ngôn ngữ cho report
    Khai báo report
    [IMG]

    • id: id duy nhất của report
    • name: tên của report (bắt buộc phải có)
    • string: tiêu đề của report (bắt buộc phải có)
    • model: report này được định nghĩa dựa vào object nào (bắt buộc phải có)
    • rml, sxw, xml, xsl: đường dẫn tới các mẫu report nguồn (đường dẫn bắt đầu từ addons), tùy vào loại report
    • auto: False nếu bạn muốn dùng parser (trình phân tích) report của bạn, bằng cách kế thừa từ lớp report_sxw.rml_parse và khai báo report như sau: report_sxw.report_sxw(report_name, object_model,rml_path,parser=customClass)
    • header: chuyển thành False để bỏ header của report (mặc định là True)
    • groups: một list các nhóm người dùng được phép xem report này, được phân cách bằng dấu phẩy “,”
    • menu: nếu True thì report này được liên kết với nút Print (mặc định là True)
    • keywords: chỉ ra từ khóa của loại report (mặc định: client_print_multi)
    [IMG]
    (Ghi rõ nguồn gốc khi bạn copy, trích dẫn từ terp.vn)
  9. admin Administrator

    Workflow
    [IMG]
    Workflow (quy trình) có thể được liên kết với bất kì đối tượng nào trong OpenERP, và chúng ta có thể hoàn toàn tùy biến chúng.
    Workflow được dùng để cấu trúc và quản lý vòng đời của các đối tượng, các tài liệu; ta có thể định nghĩa các điều kiện chuyển đổi, các trigger … bằng các công cụ đồ họa.
    Workflow (quy trình), activity (nút hoặc hành động), transition (điều kiện chuyển đổi) được khai báo bằng các bản ghi XML. Nhờ các bản ghi gọi là workitem, mà ta biết workflow đang tới đâu.
    Khai báo Workflow
    Workflow được khai báo trên các đối tượng có trường dữ liệu state (xem ví dụ class idea trong phần ORM)
    [IMG]
    • id
      • id của workflow (duy nhất)
    • name
      • tên workflow (bắt buộc)
    • osv
      • workflow này định nghĩa cho object nào (bắt buộc)
    • on_create
      • nếu True, workitem được khởi tạo tự động cho mỗi bản ghi OSV mới
    Những activity (nút) của workflow (quy trình)
    [IMG]
    • id
      • id của activity
    • wkf_id
      • id của workflow chứa activity
    • name
      • Nhãn của activity
    • flow_start
      • True thì nút này là nơi bắt đầu của một workflow
    • flow_stop
      • True thì nút này là nơi kết thúc của một workflow
    • join_mode
      • Các toán tử logic để xử lý các điều kiện chuyển đến nút này:
        • XOR: chỉ cần một điều kiện chuyển đến nút này là đúng, thì sẽ kích hoạt nút này (mặc định)
        • AND: chờ tất cả các điều kiện chuyển đến nút này là đúng hết thì mới kích hoạt nút này
    • split_mode
      • Các toán tử logic để xử lý các điều kiện đi ra khỏi nút này:
        • XOR: duyệt qua các điều kiện ra, điều kiện nào đúng đầu tiên thì sẽ chuyển đến nút tương ứng, và kích hoạt nút đó (mặc định)
        • OR: duyệt qua tất cả các điều kiện ra, điều kiện nào đúng thì sẽ chuyển đến và kích hoạt nút tương ứng (0 hoặc nhiều)
        • AND: duyệt qua tất cả các điều kiện ra; chỉ khi nào tất cả các điều kiện đều đúng thì mới cho chuyển.
    • kind
      • Loại hành động sẽ thực hiện khi các nút được kích hoạt do có 1 điều kiện chuyển đến:
        • dummy: không làm gì cả (mặc định)
        • function: gọi đến một hàm nào đó; được chỉ ra trong thuộc tính action
        • subflow để chạy một workflow con nào đó có id là subflow_id
        • stopall: dừng workflow lại
    • subflow_id
      • nếu kind là subflow thì, id sẽ là id của quy trình con cần chạy (dùng thuộc tính ref hoặc tìm với một tuple)
    • action
      • gọi đến một phương thức nào đó của object, được dùng nếu kind là function hoặc subflow. Phương thức này phải cập nhật lại trạng thái của đối tượng; ví dụ:
      • [IMG]
    Workflow Transitions (các điều kiện chuyển đổi trạng thái)
    Các điều kiện sẽ được đánh giá theo thứ tự: role_id (nhóm người dùng), signal (tín hiệu), condition expression (biểu thức điều kiện)
    [IMG]
    • act_from, act_to: chuyển từ nút nào sang nút nào
    • signal: name của button có kiểu là workflow, khi người dùng nhấn vào button này thì hệ thống sẽ kích hoạt transition này.
    • role_id: id của nhóm người dùng có quyền kích hoạt transition này.
    • condition: biểu thức python để đánh giá điều kiện; nếu là True thì hệ thống sẽ cho phép chuyển từ nút này sang nút khác.
    Các bạn cũng có thể đọc bài viết về workflow rất hay của mrkhuongcc tại đây.
    (Ghi rõ nguồn gốc khi bạn copy, trích dẫn từ terp.vn)
  10. admin Administrator

    Bảo mật
    Các cơ chế kiểm soát truy cập phải được kết hợp lại để đạt được một chính sách bảo mật chặt chẽ.
    Cơ chế kiểm soát truy cập trên nhóm
    Group (nhóm người dùng) được tạo ra như những bản ghi thông thường trên mô hình res.groups, và cấp quyền truy cập thông qua các định nghĩa menu.
    Tuy nhiên, ngay cả khi không có các menu, ta vẫn có thể truy cập gián tiếp vào các object bằng cách định nghĩa các quyền truy cập ở cấp độ object (create, read, write, unlink) cho các group. Chúng thường được chèn vào thông qua tập tin CSV bên trong mô đun. Nó cũng có thể giới hạn truy cập đến các field cụ thể trên một view hay một đối tượng bằng cách sử dụng thuộc tính nhóm của field này.
    [IMG]

    Roles (Vai trò)
    Các bản ghi trong bảng dữ liệu res_roles mô tả role, các role này chỉ được dùng trong transition của workflow thông qua thuộc tính role_id.
    (Ghi rõ nguồn gốc khi bạn copy, trích dẫn từ terp.vn)
  11. admin Administrator

    Wizards
    Wizard mô tả các phiên tương tác theo trạng thái với người dùng thông qua các form động. Ở OpenERP 5.0, wizard tận dụng osv_memory trong bộ nhớ để xây dựng các wizard từ các object và view.
    Wizard objects (osv_memory)
    Những object trong bộ nhớ được tạo ra bằng cách mở rộng osv.osv_memory:
    [IMG]

    View
    View trong wizard cũng được tạo tương tự như view của object; các button trong view của wizard có thêm thuộc tính đặc biệt là cancel; khi người dùng nhấn vào thì wizard sẽ được đóng lại.
    [IMG]

    Chạy Wizard
    Wizard cũng được gắn với một action để thực thi, trong đó có một field target dùng để hiển thị view của wizard trong một cửa sổ mới.
    [IMG]
    (Ghi rõ nguồn gốc khi bạn copy, trích dẫn từ terp.vn)
    thinhgr, dungchiunicore and Quanvm like this.
  12. dungchiunicore New Member

    Cám ơn anh vô cùng, em cũng đã tham khảo tài liệu của open erp nhưng nhiều đoạn em không cắt nghĩa được, may có bài viết của anh nên em thông ra nhiều thứ :), mong chờ từng ngày bài tiếp theo của anh. Chân thành cảm ơn anh.
    tranngocsang and admin like this.
  13. admin Administrator

    WebServices - XML​​-RPC
    Bạn có thể truy xuất OpenERP thông qua XML-RPC; thư viện để dùng XML-RPC đều có trong các ngôn ngữ lập trình.
    Ví dụ Python :
    [IMG]

    Ví dụ PHP:
    [IMG]
    (Ghi rõ nguồn gốc khi bạn copy, trích dẫn từ terp.vn)
    tranngocsang and thinhgr like this.
  14. admin Administrator

    Đa ngôn ngữ
    Trong thư mục i18n của các mô đun đều có các tập tin chứa bản dịch, các tập tin này có tên là LANG.po; trong đó LANG là mã quốc gia hoặc là mã quốc gia và mã ngôn ngữ của nước đó (ví dụ: pt.po và pt_BR.po).
    Các bản dịch sẽ được OpenERP nạp tự động cho tất cả những ngôn ngữ được kích hoạt trong hệ thống.
    Các nhà phát triển luôn sử dụng Tiếng Anh khi tạo mô đun, sau đó kết xuất các thuật ngữ trong mô đun ra, bằng cách dùng tính năng kết xuất tập tin POT trong OpenERP (Administration>Translations>Export a Translation File) để tạo ra tập tin POT mẫu; rồi chuyển các tập tin mẫu đó thành các tập tin PO để dịch.
    Nhiều IDE có các plugin hoặc chế độ để chỉnh sửa và gộp các tập tin PO/POT.
    Mẹo: Định dạng tập tin dịch thuật PO của OpenERP được tích hợp vào trang launchpad, làm cho nó trở thành nền tảng dịch thuật cộng tác trực tuyến; với các tính năng dịch tự động.
    [IMG]

    Mẹo: theo mặc định các tập tin POT được OpenERP kết xuất ra chỉ chứa các nhãn bên trong các bản ghi XML hoặc các trường dữ liệu được định nghĩa trong đoạn mã Python, nên để kết xuất bất kì chuỗi kí tự nào trong đoạn mã Python; bạn chỉ cần dùng phương thức tools.translate._ (ví dụ: _('Label'))
    (Ghi rõ nguồn gốc khi bạn copy, trích dẫn từ terp.vn)
    thinhgr thích bài này.
  15. dungchiunicore New Member

    Anh cho em hỏi một chút là trong bài của anh hay có những đoạn có ->số như thế này: related(f1, f2, …, type='float', …) trường dữ liệu kiểu này được dùng để lấy các thông tin liên quan của các trường liên kết khác.
    • f1,f2,...: các trường liên kết tới trường cần lấy thông tin(bắt buộc có f1) (→51)
    cái số 51 ở đây có ý nghĩa gì vậy anh, nếu là số trang trong quyển developer thì em tìm cũng không thấy. Em cảm ơn anh
  16. admin Administrator

    Chào dungchiunicore,
    Số 51 ở đây là dòng thứ 51 trong đoạn code tạo object idea.idea; em xem đoạn code ví dụ này trong cuốn memento nhé.
    Thân chào em!
    tranngocsang thích bài này.
  17. admin Administrator

    Phát triển ứng dụng nhanh chóng nhờ các mô đun
    Module recorder
    Mô đun base_module_record có thể được dùng để kết xuất tất cả những thay đổi trong màn hình biểu mẫu của một mô đun mới. Bạn có thể dùng nó để tùy chỉnh khi chuyển đổi, nâng cấp và cập nhật hệ thống. Nó có 2 chế độ:
    • Chế độ Bắt đầu/ Tạm dừng/ Dừng, khi đó tất cả các hoạt động, tác vụ (trên đối tượng và màn hình giao diện) sẽ được ghi nhận lại cho đến khi bạn muốn dừng lại hoặc tạm dừng.
    • Chế độ Ngày tháng - và dựa vào đối tượng, sau một khoảng thời gian định trước, hệ thống sẽ kết xuất tất cả những thay đổi trên object mà bạn đã chỉ ra.
    Mô đun tạo báo cáo (trên màn hình giao diện hoặc in ra tập tin)
    Mô đun base_report_creator có thể được dùng để tự động hóa việc tạo ra các màn hình báo cáo thống kê theo ý muốn, ví dụ như tạo ra các bảng điều khiển. Các kết quả, giá trị trên các bảng điều khiển có thể được kết xuất ra bằng cách sử dụng mô đun base_module_record.
    Mô đun base_report_designer có thể được dùng để kết nối với các plugin của OpenOffice để đưa ra giao diện thân thiện để người dùng có thể lấy dữ liệu từ OpenERP và tạo ra các mẫu báo cáo trong OpenOffice.
    Mô đun đánh giá chất lượng
    Khi viết bạn mô-đun, hãy sử dụng mô đun base_module_quality để kiểm tra các khía cạnh khác nhau của mô đun như: chuẩn của mã nguồn, các đoạn mã bị trùng, hiệu quả của mã nguồn, vv (chỉ dùng cho web client). Bạn nên tạo dữ liệu minh họa nhiều để kiểm tra.
    (Ghi rõ nguồn gốc khi bạn copy, trích dẫn từ terp.vn)
    tranngocsang thích bài này.
  18. admin Administrator

    Unit tests
    Các tập tin để thực hiện unit test là các tập tin XML thông thường trong OpenERP, các phần tử record được kết hợp với các phần tử khác như: function, workflow, và assert để kiểm thử tính lôgic, đúng sai của mô đun.

    Hệ thống sẽ tự động thực thi các đoạn unit test này và trả về phản hồi. Unit test có thể được dùng khi cài đặt mô đun; nếu bạn chỉ ra các tập tin cần chạy trong phần update_xml của tập tin mô tả mô đun (__openerp__.py)

    [IMG]

    Các thuộc tính phổ biến:

    • model: tên của object
    • id: xml_id bản ghi để kiểm tra (assert) hoặc để chuyển tới trong quy trình (workflow)
    • uid: id của người dùng (tùy chọn) để thực hiện tác vụ (function hoặc workflow)

    • assert: Thực hiện việc kiểm thử, nếu không qua được thì hệ thống sẽ báo lỗi
      • string: thông điệp báo lỗi trong trường hợp kiểm thử thất bại
      • severity: mức độ của lỗi nếu như kiểm thử thất bại (debug,info,error,warning,critical)
      • search: các điều kiện tìm kiếm để tìm được bản ghi thích hợp nếu ta không cung cấp id (mỗi bản ghi được kiểm thử)
      • count: số lượng bản ghi mong đợi do search trả về (báo lỗi nếu không khớp)
      • Phần tử con <test> với thuộc tính expr để đánh giá các biểu thức Python; các biểu thức này có thể dùng các field của object, các hàm có sẵn của Python và phương thức ref() (phương thức này trả về id csdl của một xml_id).
    • function: Gọi đến một phương thức của một object, các đối số được truyền vào phương thức thông qua các phần tử value.
      • name: tên của phương thức cần gọi
      • <value>: biểu thức python; cũng có thể dùng phương thức ref()
    • workflow: Gửi một tín hiệu (signal) tới một workflow của một object
      • ref: xml_id của object cần gửi tín hiệu tới
      • action: tên của tín hiệu được gửi tới
    Những công việc định kì
    Bảng dữ liệu ir_cron (tương ứng với đối tượng ir.cron) chứa các thông tin dùng để thiết lập các công việc định kì

    [IMG]
    --- merged: Apr 2, 2013 10:50 AM ---
    Tối ưu hóa hiệu suất
    Các hệ thống phần mềm quản lý doanh nghiệp thường phải xử lý số lượng lớn dữ liệu, nên bạn cần chú ý đến những nguyên tắc sau khi lập trình; để có được hiệu quả mong muốn:
    • Không đặt phương thức browse() trong vòng lặp for, nên đặt chúng phía trước vòng lặp và chỉ truy cập đến các đối tượng đã được duyệt tới (browsed to) bên trong vòng lặp. ORM sẽ tối ưu hóa số lượng các truy vấn cơ sở dữ liệu dựa vào các thuộc tính đã được duyệt (browsed)
    • Tránh việc sử dụng đệ quy trên hệ thống phân cấp đối tượng (thường là các đối tượng có quan hệ parent_id), bằng cách thêm các trường dữ liệu số nguyên parent_left và parent_right và thiết lập _parent_store thành True trong đối tượng của bạn. ORM sẽ dùng thuật toán duyệt tiền thứ tự trên cây để thực hiện các thao tác đệ quy các truy vấn cơ sở dữ liệu với chi phí là O(1) thay vì O(n).
    • Tránh việc sử dụng các trường dữ liệu kiểu function lung tung, đặc biệt là khi bạn đặt chúng trong màn hình danh sách (tree view). Để tối ưu hóa các trường dữ liệu kiểu function thì ta có 2 cơ chế sau:
      • multi: tất cả các trường chia sẻ cùng một giá trị trong thuộc tính multi sẽ được tính toán chung trong một lần gọi hàm, sau đó hệ thống sẽ trả về một dictonary có khóa là tên các trường, giá trị là giá trị tương ứng của từng trường đã tính toán được.
      • store: các trường kiểu function có thuộc tính store sẽ được lưu trữ trong cơ sở dữ liệu và sẽ được tính toán lại khi các thuộc tính của các đối tượng liên quan bị thay đổi. Để chỉ ra các đối tượng liên quan này, ta dùng theo cú pháp sau: store = {'model': (_ref_fnct, fields, priority)} (xem thêm ở ví dụ bên dưới)
    [IMG]

    Cộng đồng / Đóng góp
    Các dự án OpenERP được lưu trữ trên LaunchPad (LP), nơi mà tất cả các nguồn lực của dự án có thể được tìm thấy: nhánh Bazaar, theo dõi lỗi, thiết kế, lộ trình, hỏi đáp … Bạn nên tạo một tài khoản miễn phí trên launchpad.net để có thể đóng góp cho cộng đồng.
    Nhóm Launchpad:

    • Nhóm (*):
      • OpenERP Quality Team (~openerp)
    • Thành viên
      • Nhóm chủ chốt của OpenERP
    • Quyền hạn của Bazaar/LP
      • Có thể gộp và cập nhật thay đổi trên các nhánh chính thức.
    • Nhóm (*):
      • OpenERP Commiters (~openerp-commiter)
    • Thành viên
      • Những thành viên hoạt động cộng đồng được chọn ra
    • Quyền hạn của Bazaar/LP
      • Có thể đánh dấu các nhánh được gộp vào nhánh chính thức.Có thể cập nhật thay đổi trên các nhánh extra-addons
    • Nhóm (*):
      • OpenERP Drivers (~openerp-drivers)
    • Thành viên
      • Những thành viên hoạt động cộng đồng được chọn ra
    • Quyền hạn của Bazaar/LP
      • Có thể xác nhận lỗi và đặt các mốc lộ trình cho lỗi / bản thiết kế
    • Nhóm (*):
      • OpenERP Community (~openerp-community)
    • Thành viên
      • Nhóm mở, mọi người đều có thể tham gia
    • Quyền hạn của Bazaar/LP
      • Có thể tạo ra các nhánh cộng đồng nơi mà tất cả mọi người có thể đóng góp.
    (*) Thành viên của các nhóm trên cũng là thành viên của các nhóm thấp hơn.
    (Ghi rõ nguồn gốc khi bạn copy, trích dẫn từ terp.vn)
  19. dungchiunicore New Member

    order.PNG
    Capture.PNG
    Em chào anh!
    Anh cho em hỏi một chút về cái colspan và col ạ. Phía trên là giao diện của Sale order và file view của nó. Trong ô màu đỏ thì ứng với tag <group col="6" colspan="4"> theo như giải thích trong bài viết của anh thì cái Sale order sẽ chiếm 4 cột để hiển thị và nó còn phân ra cho 6 phần tử con của nó nữa. Vậy trong ô màu xanh ứng với tag <notebook colspan="5"> thì tại sao nó lại phân được như thế ạ ? Dưới nó còn những phần tử con nữa nhưng em không thấy nó sử dụng col và còn cái Sale order lines nữa, nó cũng chỉ là colspan="4" .Em mới làm quen với erp nên còn nhiều thứ chưa rõ, có câu hỏi nào ngu ngu mong anh đừng cười. Em cảm ơn anh.
    tranngocsang thích bài này.
  20. Hi bạn!
    Khi bạn dùng colspan = 5 có nghĩa là nó sẽ lấy 5 cột, nếu bạn không khai báo col thì mặc định nó lấy col = 4

Chia sẻ trang này