Framework Agreement

framework_agreement
REPOSITORY
REPOSITORYOCA/purchase-workflow
GIT
GIThttps://github.com/OCA/purchase-workflow.git
GIT FOLDER
GIT FOLDERhttps://github.com/OCA/purchase-workflow/tree/10.0/framework_agreement
VERSION
VERSION 2.0.0
CATEGORY
CATEGORYPurchase Management
LICENSE
LICENSEAGPL-3
APPLICATION
APPLICATIONNo
AUTO-INSTALLABLE
AUTO-INSTALLABLENo
AUTHORS
AUTHORSOdoo Community Association (OCA), Camptocamp
MAINTAINERS
MAINTAINERSCamptocamp
COMMITTERS
COMMITTERSPedro M. Baeza, Stéphane Bidoul (ACSONE)
WEBSITE
WEBSITEhttp://www.camptocamp.com
LAST TRACKING UPDATE
LAST TRACKING UPDATE2026-07-06 00:20:01
ODOO DEPENDENCIES
ODOO DEPENDENCIES odoo/odoo:
    - stock
    - product
    - base
    - decimal_precision
    - mail
    - base_setup
    - web_kanban
    - web
    - bus
    - web_tour
    - report
    - procurement
    - barcodes
    - web_planner
    - purchase
    - stock_account
    - account
    - analytic
PYTHON DEPENDENCIES
PYTHON DEPENDENCIES Not have
SYSTEM DEPENDENCIES
SYSTEM DEPENDENCIES Not have
DESCRIPTION
DESCRIPTION

Code Analysis

Views touched (11)
XML IDNameModelTypeStatus
add agreement setting on company add agreement setting on company res.company Inherits base.view_company_form
add_onchange_on_pruchase_order_form purchase_order_form purchase.order Inherits purchase.purchase_order_form
add_onchange_on_pruchase_order_line_form_standalone purchase_order_line_form purchase.order.line Inherits purchase.purchase_order_line_form
agreement in product view agreement in product view product.product Inherits product.product_normal_form_view
agreement_in_portfolio_form_view agreement_in_portfolio_form_view framework.agreement form New
framework_agreement_form_view framework agreement form framework.agreement form New
framework_agreement_list_view framework agreement list view framework.agreement tree New
framework_agreement_search_view framework_agreement_search_view framework.agreement search New
portfolio_form_view portfolio_form_view framework.agreement.portfolio form New
portfolio_search_view portfolio_search_view framework.agreement.portfolio search New
portfolio_tree_view portfolio_tree_view framework.agreement.portfolio tree New
HTTP endpoints (0)

No HTTP endpoints found for this module.

Models touched (9)

New fields (21)
  • available_quantity Integer
    compute='_get_available_qty' default=0 readonly=True store=True string='Available quantity'
  • clauses Html
    args: 'Clauses'
  • company_id Many2one → res.company
    readonly=True related='portfolio_id.company_id' args: 'res.company', 'Company'
  • delay Integer
    args: 'Lead time in days'
  • delivery_remark Text
    args: 'Delivery Remarks'
  • draft Boolean
    args: 'Is draft'
  • end_date Date
    args: 'End of Agreement'
  • framework_agreement_pricelist_ids One2many → framework.agreement.pricelist
    args: 'framework.agreement.pricelist', 'framework_agreement_id', 'Price lists'
  • incoterm_address Char
    args: 'Incoterm Address'
  • incoterm_id Many2one → stock.incoterms
    help='International Commercial Terms are a series of predefined commercial terms used in international transactions.' args: 'stock.incoterms', 'Incoterm'
  • name Char
    readonly=True args: 'Number'
  • origin Char
    args: 'Origin'
  • payment_term_id Many2one → account.payment.term
    args: 'account.payment.term', 'Payment Term'
  • portfolio_id Many2one → framework.agreement.portfolio
    required=True args: 'framework.agreement.portfolio', 'Portfolio'
  • product_id Many2one → product.product
    required=True args: 'product.product', 'Product'
  • purchase_line_ids One2many → purchase.order.line
    comodel_name='purchase.order.line' inverse_name='framework_agreement_id'
  • quantity Integer
    required=True args: 'Negociated quantity'
  • shipment_origin_id Many2one → res.partner
    args: 'res.partner', 'Shipment Origin'
  • start_date Date
    args: 'Begin of Agreement'
  • state Selection
    compute='_get_state' search='_search_state' selection=[('draft', 'Draft'), ('future', 'Future'), ('running', 'Running'), ('consumed', 'Consumed'), ('closed', 'Closed')] string='State'
  • supplier_id Many2one → res.partner
    readonly=True related='portfolio_id.supplier_id' args: 'res.partner', 'Supplier'
Public methods (9)
  • check_overlap(self)
    @api.one@api.constrains('supplier_id', 'product_id', 'start_date', 'end_date', 'company_id', 'incoterm_id', 'incoterm_address')
    Check that there are no similar agreements at the same time. Depending on the one_agreement_per_product flag on the company, agreements from different companies are tolerated or not.
  • create(self, vals)
    @api.model@api.returns('self', <expr>)
    We want to have increment sequence only at creation When set by a default in a o2m form default consume sequence. But we do not want to use no_gap sequence
  • get_agreement_domain(self, product_id, qty, portfolio_id=None, date_planned=None, incoterm_id=None, incoterm_address=None)
    @api.model
  • get_all_product_agreements(self, product_id, lookup_dt, qty=None)
    @api.model
    Get the all the active agreement of a given product at a given date :param product_id: product id of the product :param lookup_dt: date string of the lookup date :param qty: quantity that should be available if parameter is passed and qty is insuffisant no agreement would be returned :returns: a list of corresponding agreements or None
  • get_cheapest_agreement_for_qty(self, product_id, date, qty, currency=None)
    @api.model
    Return the cheapest agreement that has enough available qty. If not enough quantity fallback on the cheapest agreement available for quantity. :param product_id: id of the product :param date: lookup date :param qty: lookup qty :param currency: currency record to make price convertion :return: cheapest agreement and qty state :rtype: namedtuple('Cheapest', ['cheapest_agreement', 'enough'])
  • get_price(self, qty=0, currency=None)
    @api.multi
    Return price negociated for quantity :param currency: currency record :param qty: qty to lookup :returns: price float
  • get_product_agreement(self, product_id, supplier_id, lookup_dt, qty=None)
    @api.model
    Get the matching agreement for a given product/supplier at date :param product_id: id of the product :param supplier_id: supplier to look for agreement :param lookup_dt: date string of the lookup date :param qty: quantity that should be available if parameter is passed and qty is insuffisant no aggrement would be returned :returns: a corresponding agreement or None
  • has_currency(self, currency)
    @api.multi
    Predicate that check that agreement has a given currency pricelist :returns: boolean (True if a price list in given currency is present)
  • open_agreement(self, strict=True)
    @api.multi
    Open agreement Agreement goes from state draft to X

New fields (3)
  • framework_agreement_pricelist_id Many2one → framework.agreement.pricelist
    required=True args: 'framework.agreement.pricelist', 'Price list'
  • price Float
    digits=dp.get_precision('Product Price') required=True string='Negociated price'
  • quantity Integer
    required=True args: 'Quantity'
Public methods (0)

No public methods.

New fields (4)
  • agreement_ids One2many → framework.agreement
    args: 'framework.agreement', 'portfolio_id', 'Agreements'
  • company_id Many2one → res.company
    default=_company_get args: 'res.company', 'Company'
  • name Char
    required=True args: 'Name'
  • supplier_id Many2one → res.partner
    required=True args: 'res.partner', 'Supplier'
Public methods (1)
  • get_from_supplier(self, supplier)
    @api.returns('self')@api.model

New fields (3)
  • currency_id Many2one → res.currency
    required=True args: 'res.currency', 'Currency'
  • framework_agreement_id Many2one → framework.agreement
    required=True args: 'framework.agreement', 'Agreement'
  • framework_agreement_line_ids One2many → framework.agreement.line
    required=True args: 'framework.agreement.line', 'framework_agreement_pricelist_id', 'Price lines'
Public methods (0)

No public methods.

New fields (0)

No new fields.

Public methods (1)
  • price_get(self, cr, uid, ids, prod_id, qty, partner=None, context=None)
    Override of price retrieval function in order to support framework agreement. If it is a supplier price list, agreement will be taken in account and use the price of the agreement if required. If there is not enough available qty on agreement, standard price will be used. This is maybe a faulty design and we should use on_change override

New fields (1)
  • framework_agreement_ids One2many → framework.agreement
    comodel_name='framework.agreement' copy=False inverse_name='product_id' string='Framework Agreements (LTA)'
Public methods (0)

No public methods.

New fields (1)
  • portfolio_id Many2one → framework.agreement.portfolio
    domain="[('supplier_id', '=', partner_id)]" args: 'framework.agreement.portfolio', 'Portfolio'
Public methods (3)
  • onchange_partner_id(self, partner_id)
    @api.multi
    Prevent changes to the supplier if the portfolio is set. We use web_context_tunnel in order to keep the original signature.
  • update_agreements_in_lines(self)
    @api.onchange('portfolio_id', 'pricelist_id', 'date_order', 'incoterm_id')
  • update_currency_from_pricelist(self)
    @api.onchange('pricelist_id')
    Reproduce the old_api onchange_pricelist from the purchase module. We need new-style onchanges to be able to modify agreements on order lines, and we cannot have new-style and old-style onchanges at the same time.

New fields (2)
  • framework_agreement_id Many2one → framework.agreement
    domain=[('portfolio_id', '=', 'order_id.portfolio_id')] args: 'framework.agreement', 'Agreement'
  • portfolio_id Many2one → framework.agreement.portfolio
    readonly=True related='order_id.portfolio_id' args: 'framework.agreement.portfolio', 'Portfolio'
Public methods (3)
  • onchange_agreement(self)
    @api.onchange('framework_agreement_id')
  • onchange_price_unit(self)
    @api.onchange('price_unit')
  • onchange_product_id(self, pricelist_id, product_id, qty, uom_id, partner_id, date_order=False, fiscal_position_id=False, date_planned=False, name=False, price_unit=False, state='draft')
    @api.multi

New fields (1)
  • one_agreement_per_product Boolean
    help='If checked you can have only one framework agreement per product at the same time.' args: 'One agreement per product'
Public methods (0)

No public methods.

REPOSITORY
REPOSITORYOCA/purchase-workflow
GIT
GIThttps://github.com/OCA/purchase-workflow.git
GIT FOLDER
GIT FOLDERhttps://github.com/OCA/purchase-workflow/tree/9.0/framework_agreement
VERSION
VERSION 2.0.0
CATEGORY
CATEGORYPurchase Management
LICENSE
LICENSEAGPL-3
APPLICATION
APPLICATIONNo
AUTO-INSTALLABLE
AUTO-INSTALLABLENo
AUTHORS
AUTHORSOdoo Community Association (OCA), Camptocamp
MAINTAINERS
MAINTAINERSCamptocamp
COMMITTERS
COMMITTERSPedro M. Baeza, Stéphane Bidoul (ACSONE)
WEBSITE
WEBSITEhttp://www.camptocamp.com
LAST TRACKING UPDATE
LAST TRACKING UPDATE2026-07-06 00:15:23
ODOO DEPENDENCIES
ODOO DEPENDENCIES OCA/server-tools:
    - web_context_tunnel
odoo/odoo:
    - stock
    - product
    - base
    - decimal_precision
    - mail
    - base_setup
    - web_kanban
    - web
    - bus
    - report
    - procurement
    - barcodes
    - web_planner
    - purchase
    - stock_account
    - account
    - analytic
    - web_tip
PYTHON DEPENDENCIES
PYTHON DEPENDENCIES Not have
SYSTEM DEPENDENCIES
SYSTEM DEPENDENCIES Not have
DESCRIPTION
DESCRIPTION

Code Analysis

Views touched (11)
XML IDNameModelTypeStatus
add agreement setting on company add agreement setting on company res.company Inherits base.view_company_form
add_onchange_on_pruchase_order_form purchase_order_form purchase.order Inherits purchase.purchase_order_form
add_onchange_on_pruchase_order_line_form_standalone purchase_order_line_form purchase.order.line Inherits purchase.purchase_order_line_form
agreement in product view agreement in product view product.product Inherits product.product_normal_form_view
agreement_in_portfolio_form_view agreement_in_portfolio_form_view framework.agreement form New
framework_agreement_form_view framework agreement form framework.agreement form New
framework_agreement_list_view framework agreement list view framework.agreement tree New
framework_agreement_search_view framework_agreement_search_view framework.agreement search New
portfolio_form_view portfolio_form_view framework.agreement.portfolio form New
portfolio_search_view portfolio_search_view framework.agreement.portfolio search New
portfolio_tree_view portfolio_tree_view framework.agreement.portfolio tree New
HTTP endpoints (0)

No HTTP endpoints found for this module.

Models touched (9)

New fields (21)
  • available_quantity Integer
    compute='_get_available_qty' default=0 readonly=True store=True string='Available quantity'
  • clauses Html
    args: 'Clauses'
  • company_id Many2one → res.company
    readonly=True related='portfolio_id.company_id' args: 'res.company', 'Company'
  • delay Integer
    args: 'Lead time in days'
  • delivery_remark Text
    args: 'Delivery Remarks'
  • draft Boolean
    args: 'Is draft'
  • end_date Date
    args: 'End of Agreement'
  • framework_agreement_pricelist_ids One2many → framework.agreement.pricelist
    args: 'framework.agreement.pricelist', 'framework_agreement_id', 'Price lists'
  • incoterm_address Char
    args: 'Incoterm Address'
  • incoterm_id Many2one → stock.incoterms
    help='International Commercial Terms are a series of predefined commercial terms used in international transactions.' args: 'stock.incoterms', 'Incoterm'
  • name Char
    readonly=True args: 'Number'
  • origin Char
    args: 'Origin'
  • payment_term_id Many2one → account.payment.term
    args: 'account.payment.term', 'Payment Term'
  • portfolio_id Many2one → framework.agreement.portfolio
    required=True args: 'framework.agreement.portfolio', 'Portfolio'
  • product_id Many2one → product.product
    required=True args: 'product.product', 'Product'
  • purchase_line_ids One2many → purchase.order.line
    comodel_name='purchase.order.line' inverse_name='framework_agreement_id'
  • quantity Integer
    required=True args: 'Negociated quantity'
  • shipment_origin_id Many2one → res.partner
    args: 'res.partner', 'Shipment Origin'
  • start_date Date
    args: 'Begin of Agreement'
  • state Selection
    compute='_get_state' search='_search_state' selection=[('draft', 'Draft'), ('future', 'Future'), ('running', 'Running'), ('consumed', 'Consumed'), ('closed', 'Closed')] string='State'
  • supplier_id Many2one → res.partner
    readonly=True related='portfolio_id.supplier_id' args: 'res.partner', 'Supplier'
Public methods (9)
  • check_overlap(self)
    @api.one@api.constrains('supplier_id', 'product_id', 'start_date', 'end_date', 'company_id', 'incoterm_id', 'incoterm_address')
    Check that there are no similar agreements at the same time. Depending on the one_agreement_per_product flag on the company, agreements from different companies are tolerated or not.
  • create(self, vals)
    @api.model@api.returns('self', <expr>)
    We want to have increment sequence only at creation When set by a default in a o2m form default consume sequence. But we do not want to use no_gap sequence
  • get_agreement_domain(self, product_id, qty, portfolio_id=None, date_planned=None, incoterm_id=None, incoterm_address=None)
    @api.model
  • get_all_product_agreements(self, product_id, lookup_dt, qty=None)
    @api.model
    Get the all the active agreement of a given product at a given date :param product_id: product id of the product :param lookup_dt: date string of the lookup date :param qty: quantity that should be available if parameter is passed and qty is insuffisant no agreement would be returned :returns: a list of corresponding agreements or None
  • get_cheapest_agreement_for_qty(self, product_id, date, qty, currency=None)
    @api.model
    Return the cheapest agreement that has enough available qty. If not enough quantity fallback on the cheapest agreement available for quantity. :param product_id: id of the product :param date: lookup date :param qty: lookup qty :param currency: currency record to make price convertion :return: cheapest agreement and qty state :rtype: namedtuple('Cheapest', ['cheapest_agreement', 'enough'])
  • get_price(self, qty=0, currency=None)
    @api.multi
    Return price negociated for quantity :param currency: currency record :param qty: qty to lookup :returns: price float
  • get_product_agreement(self, product_id, supplier_id, lookup_dt, qty=None)
    @api.model
    Get the matching agreement for a given product/supplier at date :param product_id: id of the product :param supplier_id: supplier to look for agreement :param lookup_dt: date string of the lookup date :param qty: quantity that should be available if parameter is passed and qty is insuffisant no aggrement would be returned :returns: a corresponding agreement or None
  • has_currency(self, currency)
    @api.multi
    Predicate that check that agreement has a given currency pricelist :returns: boolean (True if a price list in given currency is present)
  • open_agreement(self, strict=True)
    @api.multi
    Open agreement Agreement goes from state draft to X

New fields (3)
  • framework_agreement_pricelist_id Many2one → framework.agreement.pricelist
    required=True args: 'framework.agreement.pricelist', 'Price list'
  • price Float
    digits=dp.get_precision('Product Price') required=True string='Negociated price'
  • quantity Integer
    required=True args: 'Quantity'
Public methods (0)

No public methods.

New fields (4)
  • agreement_ids One2many → framework.agreement
    args: 'framework.agreement', 'portfolio_id', 'Agreements'
  • company_id Many2one → res.company
    default=_company_get args: 'res.company', 'Company'
  • name Char
    required=True args: 'Name'
  • supplier_id Many2one → res.partner
    required=True args: 'res.partner', 'Supplier'
Public methods (1)
  • get_from_supplier(self, supplier)
    @api.returns('self')@api.model

New fields (3)
  • currency_id Many2one → res.currency
    required=True args: 'res.currency', 'Currency'
  • framework_agreement_id Many2one → framework.agreement
    required=True args: 'framework.agreement', 'Agreement'
  • framework_agreement_line_ids One2many → framework.agreement.line
    required=True args: 'framework.agreement.line', 'framework_agreement_pricelist_id', 'Price lines'
Public methods (0)

No public methods.

New fields (0)

No new fields.

Public methods (1)
  • price_get(self, cr, uid, ids, prod_id, qty, partner=None, context=None)
    Override of price retrieval function in order to support framework agreement. If it is a supplier price list, agreement will be taken in account and use the price of the agreement if required. If there is not enough available qty on agreement, standard price will be used. This is maybe a faulty design and we should use on_change override

New fields (1)
  • framework_agreement_ids One2many → framework.agreement
    comodel_name='framework.agreement' copy=False inverse_name='product_id' string='Framework Agreements (LTA)'
Public methods (0)

No public methods.

New fields (1)
  • portfolio_id Many2one → framework.agreement.portfolio
    domain="[('supplier_id', '=', partner_id)]" args: 'framework.agreement.portfolio', 'Portfolio'
Public methods (3)
  • onchange_partner_id(self, partner_id)
    @api.multi
    Prevent changes to the supplier if the portfolio is set. We use web_context_tunnel in order to keep the original signature.
  • update_agreements_in_lines(self)
    @api.onchange('portfolio_id', 'pricelist_id', 'date_order', 'incoterm_id')
  • update_currency_from_pricelist(self)
    @api.onchange('pricelist_id')
    Reproduce the old_api onchange_pricelist from the purchase module. We need new-style onchanges to be able to modify agreements on order lines, and we cannot have new-style and old-style onchanges at the same time.

New fields (2)
  • framework_agreement_id Many2one → framework.agreement
    domain=[('portfolio_id', '=', 'order_id.portfolio_id')] args: 'framework.agreement', 'Agreement'
  • portfolio_id Many2one → framework.agreement.portfolio
    readonly=True related='order_id.portfolio_id' args: 'framework.agreement.portfolio', 'Portfolio'
Public methods (3)
  • onchange_agreement(self)
    @api.onchange('framework_agreement_id')
  • onchange_price_unit(self)
    @api.onchange('price_unit')
  • onchange_product_id(self, pricelist_id, product_id, qty, uom_id, partner_id, date_order=False, fiscal_position_id=False, date_planned=False, name=False, price_unit=False, state='draft')
    @api.multi

New fields (1)
  • one_agreement_per_product Boolean
    help='If checked you can have only one framework agreement per product at the same time.' args: 'One agreement per product'
Public methods (0)

No public methods.

REPOSITORY
REPOSITORYOCA/purchase-workflow
GIT
GIThttps://github.com/OCA/purchase-workflow.git
GIT FOLDER
GIT FOLDERhttps://github.com/OCA/purchase-workflow/tree/8.0/framework_agreement
VERSION
VERSION 2.0.0
CATEGORY
CATEGORYPurchase Management
LICENSE
LICENSEAGPL-3
APPLICATION
APPLICATIONNo
AUTO-INSTALLABLE
AUTO-INSTALLABLENo
AUTHORS
AUTHORSOdoo Community Association (OCA), Camptocamp
MAINTAINERS
MAINTAINERSCamptocamp
COMMITTERS
COMMITTERSStéphane Bidoul, Yannick Vaucher, Alexandre Fayolle, Leonardo Pistone, Pedro M. Baeza, Stéphane Bidoul (ACSONE), OCA Transbot, oca-travis, Weblate
WEBSITE
WEBSITEhttp://www.camptocamp.com
LAST TRACKING UPDATE
LAST TRACKING UPDATE2026-07-06 00:11:26
ODOO DEPENDENCIES
ODOO DEPENDENCIES OCA/server-tools:
    - web_context_tunnel
odoo/odoo:
    - stock
    - product
    - base
    - decimal_precision
    - mail
    - base_setup
    - web_kanban
    - web
    - report
    - procurement
    - board
    - web_kanban_gauge
    - web_kanban_sparkline
    - purchase
    - stock_account
    - account
    - analytic
    - edi
    - email_template
PYTHON DEPENDENCIES
PYTHON DEPENDENCIES Not have
SYSTEM DEPENDENCIES
SYSTEM DEPENDENCIES Not have
DESCRIPTION
DESCRIPTION

Code Analysis

Views touched (11)
XML IDNameModelTypeStatus
add agreement setting on company add agreement setting on company res.company Inherits base.view_company_form
add_onchange_on_pruchase_order_form purchase_order_form purchase.order Inherits purchase.purchase_order_form
add_onchange_on_pruchase_order_line_form_standalone purchase_order_line_form purchase.order.line Inherits purchase.purchase_order_line_form
agreement in product view agreement in product view product.product Inherits product.product_normal_form_view
agreement_in_portfolio_form_view agreement_in_portfolio_form_view framework.agreement form New
framework_agreement_form_view framework agreement form framework.agreement form New
framework_agreement_list_view framework agreement list view framework.agreement tree New
framework_agreement_search_view framework_agreement_search_view framework.agreement search New
portfolio_form_view portfolio_form_view framework.agreement.portfolio form New
portfolio_search_view portfolio_search_view framework.agreement.portfolio search New
portfolio_tree_view portfolio_tree_view framework.agreement.portfolio tree New
HTTP endpoints (0)

No HTTP endpoints found for this module.

Models touched (9)

New fields (21)
  • available_quantity Integer
    compute='_get_available_qty' default=0 readonly=True store=True string='Available quantity'
  • clauses Html
    args: 'Clauses'
  • company_id Many2one → res.company
    readonly=True related='portfolio_id.company_id' args: 'res.company', 'Company'
  • delay Integer
    args: 'Lead time in days'
  • delivery_remark Text
    args: 'Delivery Remarks'
  • draft Boolean
    args: 'Is draft'
  • end_date Date
    args: 'End of Agreement'
  • framework_agreement_pricelist_ids One2many → framework.agreement.pricelist
    args: 'framework.agreement.pricelist', 'framework_agreement_id', 'Price lists'
  • incoterm_address Char
    args: 'Incoterm Address'
  • incoterm_id Many2one → stock.incoterms
    help='International Commercial Terms are a series of predefined commercial terms used in international transactions.' args: 'stock.incoterms', 'Incoterm'
  • name Char
    readonly=True args: 'Number'
  • origin Char
    args: 'Origin'
  • payment_term_id Many2one → account.payment.term
    args: 'account.payment.term', 'Payment Term'
  • portfolio_id Many2one → framework.agreement.portfolio
    required=True args: 'framework.agreement.portfolio', 'Portfolio'
  • product_id Many2one → product.product
    required=True args: 'product.product', 'Product'
  • purchase_line_ids One2many → purchase.order.line
    comodel_name='purchase.order.line' inverse_name='framework_agreement_id'
  • quantity Integer
    required=True args: 'Negociated quantity'
  • shipment_origin_id Many2one → res.partner
    args: 'res.partner', 'Shipment Origin'
  • start_date Date
    args: 'Begin of Agreement'
  • state Selection
    compute='_get_state' search='_search_state' selection=[('draft', 'Draft'), ('future', 'Future'), ('running', 'Running'), ('consumed', 'Consumed'), ('closed', 'Closed')] string='State'
  • supplier_id Many2one → res.partner
    readonly=True related='portfolio_id.supplier_id' args: 'res.partner', 'Supplier'
Public methods (9)
  • check_overlap(self)
    @api.one@api.constrains('supplier_id', 'product_id', 'start_date', 'end_date', 'company_id', 'incoterm_id', 'incoterm_address')
    Check that there are no similar agreements at the same time. Depending on the one_agreement_per_product flag on the company, agreements from different companies are tolerated or not.
  • create(self, vals)
    @api.model@api.returns('self', <expr>)
    We want to have increment sequence only at creation When set by a default in a o2m form default consume sequence. But we do not want to use no_gap sequence
  • get_agreement_domain(self, product_id, qty, portfolio_id=None, date_planned=None, incoterm_id=None, incoterm_address=None)
    @api.model
  • get_all_product_agreements(self, product_id, lookup_dt, qty=None)
    @api.model
    Get the all the active agreement of a given product at a given date :param product_id: product id of the product :param lookup_dt: date string of the lookup date :param qty: quantity that should be available if parameter is passed and qty is insuffisant no agreement would be returned :returns: a list of corresponding agreements or None
  • get_cheapest_agreement_for_qty(self, product_id, date, qty, currency=None)
    @api.model
    Return the cheapest agreement that has enough available qty. If not enough quantity fallback on the cheapest agreement available for quantity. :param product_id: id of the product :param date: lookup date :param qty: lookup qty :param currency: currency record to make price convertion :return: cheapest agreement and qty state :rtype: namedtuple('Cheapest', ['cheapest_agreement', 'enough'])
  • get_price(self, qty=0, currency=None)
    @api.multi
    Return price negociated for quantity :param currency: currency record :param qty: qty to lookup :returns: price float
  • get_product_agreement(self, product_id, supplier_id, lookup_dt, qty=None)
    @api.model
    Get the matching agreement for a given product/supplier at date :param product_id: id of the product :param supplier_id: supplier to look for agreement :param lookup_dt: date string of the lookup date :param qty: quantity that should be available if parameter is passed and qty is insuffisant no aggrement would be returned :returns: a corresponding agreement or None
  • has_currency(self, currency)
    @api.multi
    Predicate that check that agreement has a given currency pricelist :returns: boolean (True if a price list in given currency is present)
  • open_agreement(self, strict=True)
    @api.multi
    Open agreement Agreement goes from state draft to X

New fields (3)
  • framework_agreement_pricelist_id Many2one → framework.agreement.pricelist
    required=True args: 'framework.agreement.pricelist', 'Price list'
  • price Float
    digits=dp.get_precision('Product Price') required=True string='Negociated price'
  • quantity Integer
    required=True args: 'Quantity'
Public methods (0)

No public methods.

New fields (4)
  • agreement_ids One2many → framework.agreement
    args: 'framework.agreement', 'portfolio_id', 'Agreements'
  • company_id Many2one → res.company
    default=_company_get args: 'res.company', 'Company'
  • name Char
    required=True args: 'Name'
  • supplier_id Many2one → res.partner
    required=True args: 'res.partner', 'Supplier'
Public methods (1)
  • get_from_supplier(self, supplier)
    @api.returns('self')@api.model

New fields (3)
  • currency_id Many2one → res.currency
    required=True args: 'res.currency', 'Currency'
  • framework_agreement_id Many2one → framework.agreement
    required=True args: 'framework.agreement', 'Agreement'
  • framework_agreement_line_ids One2many → framework.agreement.line
    required=True args: 'framework.agreement.line', 'framework_agreement_pricelist_id', 'Price lines'
Public methods (0)

No public methods.

New fields (0)

No new fields.

Public methods (1)
  • price_get(self, cr, uid, ids, prod_id, qty, partner=None, context=None)
    Override of price retrieval function in order to support framework agreement. If it is a supplier price list, agreement will be taken in account and use the price of the agreement if required. If there is not enough available qty on agreement, standard price will be used. This is maybe a faulty design and we should use on_change override

New fields (1)
  • framework_agreement_ids One2many → framework.agreement
    comodel_name='framework.agreement' copy=False inverse_name='product_id' string='Framework Agreements (LTA)'
Public methods (0)

No public methods.

New fields (1)
  • portfolio_id Many2one → framework.agreement.portfolio
    domain="[('supplier_id', '=', partner_id)]" args: 'framework.agreement.portfolio', 'Portfolio'
Public methods (3)
  • onchange_partner_id(self, partner_id)
    @api.multi
    Prevent changes to the supplier if the portfolio is set. We use web_context_tunnel in order to keep the original signature.
  • update_agreements_in_lines(self)
    @api.onchange('portfolio_id', 'pricelist_id', 'date_order', 'incoterm_id')
  • update_currency_from_pricelist(self)
    @api.onchange('pricelist_id')
    Reproduce the old_api onchange_pricelist from the purchase module. We need new-style onchanges to be able to modify agreements on order lines, and we cannot have new-style and old-style onchanges at the same time.

New fields (2)
  • framework_agreement_id Many2one → framework.agreement
    domain=[('portfolio_id', '=', 'order_id.portfolio_id')] args: 'framework.agreement', 'Agreement'
  • portfolio_id Many2one → framework.agreement.portfolio
    readonly=True related='order_id.portfolio_id' args: 'framework.agreement.portfolio', 'Portfolio'
Public methods (3)
  • onchange_agreement(self)
    @api.onchange('framework_agreement_id')
  • onchange_price_unit(self)
    @api.onchange('price_unit')
  • onchange_product_id(self, pricelist_id, product_id, qty, uom_id, partner_id, date_order=False, fiscal_position_id=False, date_planned=False, name=False, price_unit=False, state='draft')
    @api.multi

New fields (1)
  • one_agreement_per_product Boolean
    help='If checked you can have only one framework agreement per product at the same time.' args: 'One agreement per product'
Public methods (0)

No public methods.

REPOSITORY
REPOSITORYOCA/purchase-workflow
GIT
GIThttps://github.com/OCA/purchase-workflow.git
GIT FOLDER
GIT FOLDERhttps://github.com/OCA/purchase-workflow/tree/7.0/framework_agreement
VERSION
VERSION 0.1.1
CATEGORY
CATEGORYPurchase Management
LICENSE
LICENSEAGPL-3
APPLICATION
APPLICATIONNo
AUTO-INSTALLABLE
AUTO-INSTALLABLENo
AUTHORS
AUTHORSOdoo Community Association (OCA), Camptocamp
MAINTAINERS
MAINTAINERSCamptocamp
COMMITTERS
COMMITTERSStéphane Bidoul, Yannick Vaucher, Romain Deheele, Alexandre Fayolle, Leonardo Pistone, Nicolas Bessi, unknown, OCA Transbot
WEBSITE
WEBSITEhttp://www.camptocamp.com
LAST TRACKING UPDATE
LAST TRACKING UPDATE2026-07-06 00:07:15
ODOO DEPENDENCIES
ODOO DEPENDENCIES odoo/odoo:
    - stock
    - product
    - base
    - process
    - web
    - decimal_precision
    - mail
    - base_setup
    - web_kanban
    - account
    - analytic
    - board
    - edi
    - email_template
    - procurement
    - purchase
PYTHON DEPENDENCIES
PYTHON DEPENDENCIES Not have
SYSTEM DEPENDENCIES
SYSTEM DEPENDENCIES Not have
DESCRIPTION
DESCRIPTION
Long Term Agreement (or Framework Agreement) on price.
======================================================

Agreements are defined by a product, a date range , a supplier, a price, a lead
time and agreed quantity.

Agreements are set on a product view or using a menu in the product
configuration.

There can be only one agreement for the same supplier and product at the same
time, even if we may have different prices depending on lead time/qty.

There is an option on company to restrict one agreement per product at same
time.

If an agreement is running its price will be automatically used in PO.  A
warning will be raised in case of exhaustion of override of agreement price.

**Technical aspect**

The module provide an observalbe mixin to enable generic on_change management
on various model related to agreements.

The framework agreement is by default related to purchase order but the addon
provides a library to integrate it with any other model easily

Code Analysis

Views touched (6)
XML IDNameModelTypeStatus
add agreement setting on company add agreement setting on company res.company Inherits base.view_company_form
add_onchange_on_pruchase_order_form add onchange on pruchase form purchase.order Inherits purchase.purchase_order_form
add_onchange_on_pruchase_order_line_form_standalone add onchange on pruchase order line form standalone purchase.order.line Inherits purchase.purchase_order_line_form
agreement in product view agreement in product view product.product group Inherits product.product_normal_form_view
framework_agreement_form_view framework agreement form framework.agreement form New
framework_agreement_list_view framework agreement list view framework.agreement tree New
HTTP endpoints (0)

No HTTP endpoints found for this module.

Models touched (8)

New fields (0)

No new fields.

Public methods (9)
  • check_overlap(self, cr, uid, ids, context=None)
    Constraint to check that no agreements for same product/supplier overlap. One agreement per product limit is checked if one_agreement_per_product is set to True on company
  • create(self, cr, uid, vals, context=None)
    We want to have increment sequence only at creation When set by a default in a o2m form default consume sequence. But we do not want to use no_gap sequence
  • date_valid(self, cr, uid, agreement_id, date, context=None)
    Predicate that checks that date is in agreement :param date: date to validate :returns: True if date is valid
  • get_all_product_agreements(self, cr, uid, product_id, lookup_dt, qty=None, context=None)
    Get the all the active agreement of a given product at a given date :param product_id: product id of the product :param lookup_dt: date string of the lookup date :param qty: quantity that should be available if parameter is passed and qty is insuffisant no agreement would be returned :returns: a list of corresponding agreements or None
  • get_cheapest_agreement_for_qty(self, cr, uid, product_id, date, qty, currency=None, context=None)
    Return the cheapest agreement that has enough available qty. If not enough quantity fallback on the cheapest agreement available for quantity. :param product_id: :param date: :param qty: :param currency: currency record to make price convertion returns (cheapest agreement, enough qty)
  • get_price(self, cr, uid, agreement_id, qty=0, currency=None, context=None)
    Return price negociated for quantity :param currency: currency record :param qty: qty to lookup :returns: price float
  • get_product_agreement(self, cr, uid, product_id, supplier_id, lookup_dt, qty=None, context=None)
    Get the matching agreement for a given product/supplier at date :param product_id: product id of the product :param supplier_id: supplier to look for agreement :param lookup_dt: date string of the lookup date :param qty: quantity that should be available if parameter is passed and qty is insuffisant no aggrement would be returned :returns: a corresponding agreement or None
  • has_currency(self, cr, uid, agr_id, currency, context=None)
    Predicate that check that agreement has a given currency pricelist :returns: boolean (True if a price list in given currency is present)
  • open_agreement(self, cr, uid, ids, context=None)
    Open agreement Agreement goes from state draft to X

New fields (0)

No new fields.

Public methods (0)

No public methods.

New fields (0)

No new fields.

Public methods (0)

No public methods.

New fields (0)

No new fields.

Public methods (1)
  • price_get(self, cr, uid, ids, prod_id, qty, partner=None, context=None)
    Override of price retrival function in order to support framework agreement. If it is a supplier price list agrreement will be taken in account and use the price of the agreement if required. If there is not enough available qty on agreement, standard price will be used. This is mabye a faulty design and we should use on_change override

New fields (0)

No new fields.

Public methods (1)
  • copy(self, cr, uid, id, default=None, context=None)
    Override of copy in order not to copy agreements

New fields (0)

No new fields.

Public methods (4)
  • onchange_agreement(self, cr, uid, ids, agreement_id, partner_id, date, context=None)
  • onchange_date(self, cr, uid, ids, agreement_id, date, context=None)
    Check that date is in agreement
  • onchange_partner_id(self, cr, uid, ids, partner_id, agreement_id)
    Override to ensure that partner can not be changed if agreement
  • onchange_pricelist(self, cr, uid, ids, pricelist_id, line_ids, context=None)

New fields (0)

No new fields.

Public methods (2)
  • onchange_price(self, cr, uid, ids, price, agreement_id, qty, pricelist_id, product_id, context=None)
    Raise a warning if a agreed price is changed
  • onchange_product_id(self, cr, uid, ids, pricelist_id, product_id, qty, uom_id, partner_id, date_order=False, fiscal_position_id=False, date_planned=False, name=False, price_unit=False, context=None, agreement_id=False, **kwargs)
    We override this function to check qty change (I know...) The price retrieval is managed by the override of product.pricelist.price_get that is overidden to support agreement. This is mabye a faulty design as it has a low level impact

New fields (0)

No new fields.

Public methods (0)

No public methods.