Browse Source

[ADD]Initial Commit

pull/11/merge
SHEREEF PT 8 years ago
parent
commit
9d9ecbb99d
  1. 24
      purchase_recurring_orders/__init__.py
  2. 46
      purchase_recurring_orders/__openerp__.py
  3. 54
      purchase_recurring_orders/data/recurring_orders_data.xml
  4. 24
      purchase_recurring_orders/models/__init__.py
  5. 93
      purchase_recurring_orders/models/purchase_order.py
  6. 428
      purchase_recurring_orders/models/recurring_orders.py
  7. 4
      purchase_recurring_orders/security/ir.model.access.csv
  8. BIN
      purchase_recurring_orders/static/description/81.1.png
  9. BIN
      purchase_recurring_orders/static/description/81.png
  10. BIN
      purchase_recurring_orders/static/description/82.png
  11. BIN
      purchase_recurring_orders/static/description/83.png
  12. BIN
      purchase_recurring_orders/static/description/banner.jpg
  13. BIN
      purchase_recurring_orders/static/description/cybro_logo.png
  14. BIN
      purchase_recurring_orders/static/description/icon.png
  15. 79
      purchase_recurring_orders/static/description/index.html
  16. 2
      purchase_recurring_orders/tests/__init__.py
  17. 32
      purchase_recurring_orders/tests/test_recurring_orders.py
  18. 28
      purchase_recurring_orders/views/purchase_order_view.xml
  19. 170
      purchase_recurring_orders/views/recurring_orders_view.xml
  20. 33
      purchase_recurring_orders/views/res_partner_view.xml
  21. 23
      purchase_recurring_orders/wizard/__init__.py
  22. 55
      purchase_recurring_orders/wizard/renew_wizard.py
  23. 33
      purchase_recurring_orders/wizard/renew_wizard_view.xml

24
purchase_recurring_orders/__init__.py

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
# Copyright (C) 2009-TODAY Cybrosys Technologies(<http://www.cybrosys.com>).
# Author: Jesni Banu(<http://www.cybrosys.com>)
# you can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# It is forbidden to publish, distribute, sublicense, or sell copies
# of the Software or modified copies of the Software.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# GENERAL PUBLIC LICENSE (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
###############################################################################
from . import models
from . import wizard

46
purchase_recurring_orders/__openerp__.py

@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
# Copyright (C) 2009-TODAY Cybrosys Technologies(<http://www.cybrosys.com>).
# Author: Jesni Banu(<http://www.cybrosys.com>)
# you can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# It is forbidden to publish, distribute, sublicense, or sell copies
# of the Software or modified copies of the Software.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# GENERAL PUBLIC LICENSE (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
###############################################################################
{
'name': 'Purchase Recurring orders',
'version': '8.0.1.0.0',
'category': 'Purchase Management',
'summary': 'Recurring orders for Selected Purchase Order',
'description': 'This module allows you to create recurring orders for purchase.',
'author': 'Cybrosys Techno Solutions',
'company': 'Cybrosys Techno Solutions',
'website': 'http://www.cybrosys.com',
'depends': ['purchase'],
'data': [
'security/ir.model.access.csv',
'data/recurring_orders_data.xml',
'wizard/renew_wizard_view.xml',
'views/recurring_orders_view.xml',
'views/purchase_order_view.xml',
'views/res_partner_view.xml',
],
'license': 'AGPL-3',
'images': ['static/description/banner.jpg'],
'installable': True,
'auto_install': False,
'application': False,
}

54
purchase_recurring_orders/data/recurring_orders_data.xml

@ -0,0 +1,54 @@
<?xml version="1.0"?>
<openerp>
<data noupdate="1">
<record model="ir.sequence.type" id="seq_type_ro_agreement">
<field name="name">Agreement sequence</field>
<field name="code">purchase.r_o.agreement.sequence</field>
</record>
<record model="ir.sequence" id="seq_ro_agreement">
<field name="name">Agreement sequence</field>
<field name="code">purchase.r_o.agreement.sequence</field>
<field name="padding" eval="4"/>
<field name="prefix">AG-%(y)s-</field>
</record>
<record model="ir.cron" id="cron_recurring_orders_prolong_check">
<field name="name">Prolongation check for recurring orders agreements</field>
<field name="interval_number">1</field>
<field name="priority">10</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field name="doall" eval="False"/>
<field name="model" eval="'purchase.recurring_orders.agreement'"/>
<field name="function" eval="'revise_agreements_expirations_planned'"/>
<field name="args" eval="'()'"/>
</record>
<record model="ir.cron" id="cron_recurring_orders_confirm_orders">
<field name="name">Confirm current orders</field>
<field name="interval_number">1</field>
<field name="priority">10</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field name="doall" eval="False"/>
<field name="model" eval="'purchase.recurring_orders.agreement'"/>
<field name="function" eval="'confirm_current_orders_planned'"/>
<field name="args" eval="'()'"/>
</record>
<record model="ir.cron" id="cron_recurring_orders_generate_orders">
<field name="name">Generate recurring orders for next year</field>
<field name="interval_number">1</field>
<field name="priority">10</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field name="doall" eval="False"/>
<field name="model" eval="'purchase.recurring_orders.agreement'"/>
<field name="function" eval="'generate_next_orders_planned'"/>
<field name="args" eval="'(1, )'"/>
</record>
</data>
</openerp>

24
purchase_recurring_orders/models/__init__.py

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
# Copyright (C) 2009-TODAY Cybrosys Technologies(<http://www.cybrosys.com>).
# Author: Jesni Banu(<http://www.cybrosys.com>)
# you can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# It is forbidden to publish, distribute, sublicense, or sell copies
# of the Software or modified copies of the Software.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# GENERAL PUBLIC LICENSE (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
###############################################################################
from . import recurring_orders
from . import purchase_order

93
purchase_recurring_orders/models/purchase_order.py

@ -0,0 +1,93 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
# Copyright (C) 2009-TODAY Cybrosys Technologies(<http://www.cybrosys.com>).
# Author: Jesni Banu(<http://www.cybrosys.com>)
# you can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# It is forbidden to publish, distribute, sublicense, or sell copies
# of the Software or modified copies of the Software.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# GENERAL PUBLIC LICENSE (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
###############################################################################
from openerp import models, fields, api
class PurchaseOrder(models.Model):
_inherit = 'purchase.order'
@api.model
def _prepare_agreement_vals(self, order):
return {
'name': order.name,
'partner_id': order.partner_id.id,
'company_id': order.company_id.id,
'start_date': fields.Datetime.now(),
}
@api.model
def _prepare_agreement_line_vals(self, order_line, agreement):
return {
'agreement_id': agreement.id,
'product_id': order_line.product_id.id,
'quantity': order_line.product_qty,
}
@api.multi
def action_button_generate_agreement(self):
agreements = []
agreement_obj = self.env['purchase.recurring_orders.agreement']
agreement_line_obj = self.env['purchase.recurring_orders.agreement.line']
for purchase_order in self:
agreement_vals = self._prepare_agreement_vals(purchase_order)
agreement = agreement_obj.create(agreement_vals)
agreements.append(agreement)
for order_line in purchase_order.order_line:
agreement_line_vals = self._prepare_agreement_line_vals(
order_line, agreement)
agreement_line_obj.create(agreement_line_vals)
if len(agreements) == 1:
view = self.env.ref(
'purchase_recurring_orders.'
'view_purchase_recurring_orders_agreement_form')
return {
'type': 'ir.actions.act_window',
'res_model': 'purchase.recurring_orders.agreement',
'res_id': agreement[0].id,
'view_type': 'form',
'view_mode': 'form',
'view_id': view.id,
'target': 'current',
'nodestroy': True,
}
return True
from_agreement = fields.Boolean(
string='From agreement?', copy=False,
help='This field indicates if the purchase order comes from an agreement.')
agreement_id = fields.Many2one(
comodel_name='purchase.recurring_orders.agreement',
string='Agreement reference', ondelete='restrict')
@api.multi
def view_order(self):
return {
'view_type': 'form',
'view_mode': 'form',
'res_model': 'purchase.order',
'context': self.env.context,
'res_id': self[:1].id,
'view_id': [self.env.ref('purchase.purchase_order_form').id],
'type': 'ir.actions.act_window',
'nodestroy': True
}

428
purchase_recurring_orders/models/recurring_orders.py

@ -0,0 +1,428 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
# Copyright (C) 2009-TODAY Cybrosys Technologies(<http://www.cybrosys.com>).
# Author: Jesni Banu(<http://www.cybrosys.com>)
# you can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# It is forbidden to publish, distribute, sublicense, or sell copies
# of the Software or modified copies of the Software.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# GENERAL PUBLIC LICENSE (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
###############################################################################
from datetime import timedelta
from dateutil.relativedelta import relativedelta
from openerp import models, fields, api, exceptions, _
import openerp.addons.decimal_precision as dp
class Agreement(models.Model):
_name = 'purchase.recurring_orders.agreement'
_inherit = ['mail.thread']
_description = "Recurring orders agreement"
@api.model
def __get_next_term_date(self, date, unit, interval):
if unit == 'days':
return date + timedelta(days=interval)
elif unit == 'weeks':
return date + timedelta(weeks=interval)
elif unit == 'months':
return date + relativedelta(months=interval)
elif unit == 'years':
return date + relativedelta(years=interval)
@api.multi
def _compute_next_expiration_date(self):
for agreement in self:
if agreement.prolong == 'fixed':
agreement.next_expiration_date = agreement.end_date
elif agreement.prolong == 'unlimited':
now = fields.Date.from_string(fields.Date.today())
date = self.__get_next_term_date(
fields.Date.from_string(agreement.start_date),
agreement.prolong_unit, agreement.prolong_interval)
while date < now:
date = self.__get_next_term_date(
date, agreement.prolong_unit,
agreement.prolong_interval)
agreement.next_expiration_date = date
else:
agreement.next_expiration_date = self.__get_next_term_date(
fields.Date.from_string(agreement.last_renovation_date or
agreement.start_date),
agreement.prolong_unit, agreement.prolong_interval)
def _default_company_id(self):
company_model = self.env['res.company']
company_id = company_model._company_default_get('purchase')
return company_model.browse(company_id)
name = fields.Char(
string='Name', size=100, index=True, required=True,
help='Name that helps to identify the agreement')
number = fields.Char(
string='Agreement number', index=True, size=32, copy=False,
help="Number of agreement. Keep empty to get the number assigned by a "
"sequence.")
active = fields.Boolean(
string='Active', default=True,
help='Unchecking this field, quotas are not generated')
partner_id = fields.Many2one(
comodel_name='res.partner', string='Supplier', index=True,
change_default=True, required=True,
help="Supplier you are making the agreement with")
company_id = fields.Many2one(
comodel_name='res.company', string='Company', required=True,
help="Company that signs the agreement", default=_default_company_id)
start_date = fields.Date(
string='Start date', index=True, copy=False,
help="Beginning of the agreement. Keep empty to use the current date")
prolong = fields.Selection(
selection=[('recurrent', 'Renewable fixed term'),
('unlimited', 'Unlimited term'),
('fixed', 'Fixed term')],
string='Prolongation', default='unlimited',
help="Sets the term of the agreement. 'Renewable fixed term': It sets "
"a fixed term, but with possibility of manual renew; 'Unlimited "
"term': Renew is made automatically; 'Fixed term': The term is "
"fixed and there is no possibility to renew.", required=True)
end_date = fields.Date(
string='End date', help="End date of the agreement")
prolong_interval = fields.Integer(
string='Interval', default=1,
help="Interval in time units to prolong the agreement until new "
"renewable (that is automatic for unlimited term, manual for "
"renewable fixed term).")
prolong_unit = fields.Selection(
selection=[('days', 'days'),
('weeks', 'weeks'),
('months', 'months'),
('years', 'years')],
string='Interval unit', default='years',
help='Time unit for the prolongation interval')
agreement_line = fields.One2many(
comodel_name='purchase.recurring_orders.agreement.line',
inverse_name='agreement_id', string='Agreement lines')
order_line = fields.One2many(
comodel_name='purchase.order', copy=False, inverse_name='agreement_id',
string='Orders', readonly=True)
renewal_line = fields.One2many(
comodel_name='purchase.recurring_orders.agreement.renewal', copy=False,
inverse_name='agreement_id', string='Renewal lines', readonly=True)
last_renovation_date = fields.Date(
string='Last renovation date',
help="Last date when agreement was renewed (same as start date if not "
"renewed)")
next_expiration_date = fields.Date(
compute="_compute_next_expiration_date", string='Next expiration date')
state = fields.Selection(
selection=[('empty', 'Without orders'),
('first', 'First order created'),
('orders', 'With orders')],
string='State', readonly=True, default='empty')
renewal_state = fields.Selection(
selection=[('not_renewed', 'Agreement not renewed'),
('renewed', 'Agreement renewed')],
string='Renewal state', readonly=True, default='not_renewed')
notes = fields.Text('Notes')
_sql_constraints = [
('number_uniq', 'unique(number)', 'Agreement number must be unique !'),
]
@api.constrains('start_date', 'end_date')
def _check_dates(self):
for record in self:
if record.end_date and record.end_date < record.start_date:
raise exceptions.Warning(
_('Agreement end date must be greater than start date'))
@api.model
def create(self, vals):
if not vals.get('start_date'):
vals['start_date'] = fields.Date.today()
if not vals.get('number'):
vals['number'] = self.env['ir.sequence'].get(
'purchase.r_o.agreement.sequence')
return super(Agreement, self).create(vals)
@api.multi
def write(self, vals):
value = super(Agreement, self).write(vals)
if (any(vals.get(x) is not None for x in
['active', 'number', 'agreement_line', 'prolong', 'end_date',
'prolong_interval', 'prolong_unit', 'partner_id'])):
self.unlink_orders(fields.Date.today())
return value
@api.model
def copy(self, id, default=None):
agreement_record = self.browse(id)
default.update({
'state': 'empty',
'active': True,
'name': '%s*' % agreement_record['name'],
})
return super(Agreement, self).copy(id, default=default)
@api.multi
def unlink(self):
for agreement in self:
if any(agreement.mapped('order_line')):
raise exceptions.Warning(
_('You cannot remove agreements with confirmed orders!'))
self.unlink_orders(fields.Date.from_string(fields.Date.today()))
return models.Model.unlink(self)
@api.multi
def onchange_start_date(self, start_date=False):
if not start_date:
return {}
result = {'value': {'last_renovation_date': start_date}}
return result
@api.model
def revise_agreements_expirations_planned(self):
for agreement in self.search([('prolong', '=', 'unlimited')]):
if agreement.next_expiration_date <= fields.Date.today():
agreement.write({'prolong': 'unlimited'})
return True
@api.model
def _prepare_purchase_order_vals(self, agreement, date):
order_obj = self.env['purchase.order'].with_context(
company_id=agreement.company_id.id)
order_vals = {
'date_order': date,
'date_confirm': date,
'origin': agreement.number,
'partner_id': agreement.partner_id.id,
'state': 'draft',
'company_id': agreement.company_id.id,
'from_agreement': True,
'agreement_id': agreement.id,
'location_id': 1,
}
order_vals.update(order_obj.onchange_partner_id(
agreement.partner_id.id)['value'])
order_vals['user_id'] = agreement.partner_id.user_id.id
return order_vals
@api.model
def _prepare_purchase_order_line_vals(self, agreement_line, order):
order_line_obj = self.env['purchase.order.line'].with_context(
company_id=self.company_id.id)
order_line_vals = {
'order_id': order.id,
'product_id': agreement_line.product_id.id,
'product_qty': agreement_line.quantity,
}
order_line_vals.update(order_line_obj.product_id_change(
pricelist_id=order.pricelist_id.id,
product_id= agreement_line.product_id.id,
qty=agreement_line.quantity,
uom_id=agreement_line.uom_id,
partner_id=order.partner_id.id,
fiscal_position_id=order.fiscal_position.id)['value'])
if agreement_line.specific_price:
order_line_vals['price_unit'] = agreement_line.specific_price
order_line_vals['taxes_id'] = [(6, 0, tuple(order_line_vals['taxes_id']))]
if agreement_line.additional_description:
order_line_vals['name'] += " %s" % (
agreement_line.additional_description)
return order_line_vals
@api.multi
def create_order(self, date, agreement_lines):
self.ensure_one()
order_line_obj = self.env['purchase.order.line'].with_context(
company_id=self.company_id.id)
order_vals = self._prepare_purchase_order_vals(self, date)
order = self.env['purchase.order'].create(order_vals)
for agreement_line in agreement_lines:
order_line_vals = self._prepare_purchase_order_line_vals(
agreement_line, order)
order_line_obj.create(order_line_vals)
agreement_lines.write({'last_order_date': fields.Date.today()})
if self.state != 'orders':
self.state = 'orders'
return order
@api.multi
def _get_next_order_date(self, line, start_date):
self.ensure_one()
next_date = fields.Date.from_string(self.start_date)
while next_date <= start_date:
next_date = self.__get_next_term_date(
next_date, line.ordering_unit, line.ordering_interval)
return next_date
@api.multi
def generate_agreement_orders(self, start_date, end_date):
self.ensure_one()
if not self.active:
return
lines_to_order = {}
exp_date = fields.Date.from_string(self.next_expiration_date)
if exp_date < end_date and self.prolong != 'unlimited':
end_date = exp_date
for line in self.agreement_line:
if not line.active_chk:
continue
next_order_date = self._get_next_order_date(line, start_date)
while next_order_date <= end_date:
if not lines_to_order.get(next_order_date):
lines_to_order[next_order_date] = self.env[
'purchase.recurring_orders.agreement.line']
lines_to_order[next_order_date] |= line
next_order_date = self._get_next_order_date(
line, next_order_date)
dates = lines_to_order.keys()
dates.sort()
for date in dates:
order = self.order_line.filtered(
lambda x: (
fields.Date.to_string(
fields.Datetime.from_string(x.date_order)) ==
fields.Date.to_string(date)))
if not order:
self.create_order(
fields.Date.to_string(date), lines_to_order[date])
@api.multi
def generate_initial_order(self):
self.ensure_one()
agreement_lines = self.mapped('agreement_line').filtered('active_chk')
order = self.create_order(self.start_date, agreement_lines)
self.write({'state': 'first'})
order.signal_workflow('order_confirm')
return {
'domain': "[('id', '=', %s)]" % order.id,
'view_type': 'form',
'view_mode': 'form',
'res_model': 'purchase.order',
'context': self.env.context,
'res_id': order.id,
'view_id': [self.env.ref('purchase.purchase_order_form').id],
'type': 'ir.actions.act_window',
'nodestroy': True
}
@api.model
def generate_next_orders_planned(self, years=1, start_date=None):
if start_date:
start_date = fields.Date.from_string(start_date)
self.search([]).generate_next_orders(
years=years, start_date=start_date)
@api.multi
def generate_next_year_orders(self):
return self.generate_next_orders(years=1)
@api.multi
def generate_next_orders(self, years=1, start_date=None):
if not start_date:
start_date = fields.Date.from_string(fields.Date.today())
end_date = start_date + relativedelta(years=years)
for agreement in self:
agreement.generate_agreement_orders(start_date, end_date)
return True
@api.model
def confirm_current_orders_planned(self):
tomorrow = fields.Date.to_string(
fields.Date.from_string(fields.Date.today()) + timedelta(days=1))
orders = self.env['purchase.order'].search([
('agreement_id', '!=', False),
('state', 'in', ('draft', 'sent')),
('date_order', '<', tomorrow)
])
for order in orders:
order.signal_workflow('order_confirm')
@api.multi
def unlink_orders(self, start_date):
orders = self.mapped('order_line').filtered(
lambda x: (x.state in ('draft', 'sent') and
x.date_order >= start_date))
orders.unlink()
class AgreementLine(models.Model):
_name = 'purchase.recurring_orders.agreement.line'
uom_id = fields.Many2one('product_uom', string="Uom")
active_chk = fields.Boolean(
string='Active', default=True,
help='Unchecking this field, this quota is not generated')
agreement_id = fields.Many2one(
comodel_name='purchase.recurring_orders.agreement',
string='Agreement reference', ondelete='cascade')
product_id = fields.Many2one(
comodel_name='product.product', string='Product', ondelete='set null',
required=True)
name = fields.Char(
related="product_id.name", string='Description', store=False)
additional_description = fields.Char(
string='Add. description', size=30,
help='Additional description that will be added to the product '
'description on orders.')
quantity = fields.Float(
string='Quantity', required=True, help='Quantity of the product',
default=1.0)
discount = fields.Float(string='Discount (%)', digits=(16, 2))
ordering_interval = fields.Integer(
string='Interval', required=True, default=1,
help="Interval in time units for making an order of this product")
ordering_unit = fields.Selection(
selection=[('days', 'days'),
('weeks', 'weeks'),
('months', 'months'),
('years', 'years')],
string='Interval unit', required=True, default='months')
last_order_date = fields.Date(
string='Last order', help='Date of the last Purchase order generated')
specific_price = fields.Float(
string='Specific price', digits_compute=dp.get_precision('Purchase Price'),
help='Specific price for this product. Keep empty to use the list '
'price while generating order')
list_price = fields.Float(
related='product_id.list_price', string="List price", readonly=True)
_sql_constraints = [
('line_qty_zero', 'CHECK (quantity > 0)',
'All product quantities must be greater than 0.\n'),
('line_interval_zero', 'CHECK (ordering_interval > 0)',
'All ordering intervals must be greater than 0.\n'),
]
@api.multi
def onchange_product_id(self, product_id=False):
result = {}
if product_id:
product = self.env['product.product'].browse(product_id)
if product:
result['value'] = {'name': product['name']}
return result
class AgreementRenewal(models.Model):
_name = 'purchase.recurring_orders.agreement.renewal'
agreement_id = fields.Many2one(
comodel_name='purchase.recurring_orders.agreement',
string='Agreement reference', ondelete='cascade', select=True)
date = fields.Date(string='Date', help="Date of the renewal")
comments = fields.Char(
string='Comments', size=200, help='Renewal comments')

4
purchase_recurring_orders/security/ir.model.access.csv

@ -0,0 +1,4 @@
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
"access_purchase_recurring_orders_agreement","purchase.recurring_orders.agreement","model_purchase_recurring_orders_agreement","base.group_sale_salesman",1,1,1,1
"access_purchase_recurring_orders_agreement_line","purchase.recurring_orders.agreement.line","model_purchase_recurring_orders_agreement_line","base.group_sale_salesman",1,1,1,1
"access_purchase_recurring_orders_agreement_renewal","purchase.recurring_orders.agreement.renewal","model_purchase_recurring_orders_agreement_renewal","base.group_sale_salesman",1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_purchase_recurring_orders_agreement purchase.recurring_orders.agreement model_purchase_recurring_orders_agreement base.group_sale_salesman 1 1 1 1
3 access_purchase_recurring_orders_agreement_line purchase.recurring_orders.agreement.line model_purchase_recurring_orders_agreement_line base.group_sale_salesman 1 1 1 1
4 access_purchase_recurring_orders_agreement_renewal purchase.recurring_orders.agreement.renewal model_purchase_recurring_orders_agreement_renewal base.group_sale_salesman 1 1 1 1

BIN
purchase_recurring_orders/static/description/81.1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

BIN
purchase_recurring_orders/static/description/81.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

BIN
purchase_recurring_orders/static/description/82.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

BIN
purchase_recurring_orders/static/description/83.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

BIN
purchase_recurring_orders/static/description/banner.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

BIN
purchase_recurring_orders/static/description/cybro_logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

BIN
purchase_recurring_orders/static/description/icon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

79
purchase_recurring_orders/static/description/index.html

@ -0,0 +1,79 @@
<section class="oe_container">
<div class="oe_row oe_spaced">
<h2 class="oe_slogan">Purchase Recurring Order</h2>
<h3 class="oe_slogan">This module allows you to create recurring orders for Purchases</h3>
<h4 class="oe_slogan">Cybrosys Techno Solutions , www.cybrosys.com</h4>
</div>
<div class="oe_row oe_spaced">
<div>
&#x261B;This module is useful for create recurring orders for Purchases.
Based on information provided it will create purchase orders recurrently.
</div>
</div>
<div class="oe_row oe_spaced">
<div class="oe_span12">
<p class='oe_mt32'>
&#x261B; Go to Purchases -> Purchase -> Recurring order agreements. <br/>
Here you can fill all the information related to Recurring order agreements. Once saved, you can generate initial purchase order.
</p>
</div>
<div class="oe_span12">
<div class="oe_demo oe_picture oe_screenshot">
<img src="81.png">
</div>
</div>
</div>
</section>
<section class="oe_container oe_dark">
<div class="oe_row oe_spaced">
<div class="oe_span12">
<p class='oe_mt32'>
&#x261B; Here you can generate initial purchase order through "Generate initial Order" button.</p>
</div>
<div class="oe_span6">
<div class="oe_demo oe_picture oe_screenshot">
<img src="81.1.png">
</div>
</div>
<div class="oe_span6">
<div class="oe_demo oe_picture oe_screenshot">
<img src="82.png">
</div>
</div>
</div>
</section>
<section class="oe_container">
<div class="oe_row oe_spaced">
<div class="oe_span12">
<p class='oe_mt32'>
&#x261B; You can also create recurring orders from purchase order through "Generate agreement" button.
</p>
</div>
<div class="oe_span12">
<div class="oe_demo oe_picture oe_screenshot">
<img src="83.png">
</div>
</div>
</div>
</section>
<section class="oe_container oe_dark">
<h2 class="oe_slogan" style="margin-top:20px;" >Need Any Help.?</h2>
<div class="oe_slogan" style="margin-top:10px !important;">
<a class="btn btn-primary btn-lg mt8"
style="color: #FFFFFF !important;" href="http://www.cybrosys.com"><i
class="fa fa-envelope"></i> Email </a> <a
class="btn btn-primary btn-lg mt8" style="color: #FFFFFF !important;"
href="http://www.cybrosys.com/contact/"><i
class="fa fa-phone"></i> Contact Us </a> <a
class="btn btn-primary btn-lg mt8" style="color: #FFFFFF !important;"
href="http://www.cybrosys.com/odoo-customization-and-installation/"><i
class="fa fa-check-square"></i> Request Customization </a>
</div>
<img src="cybro_logo.png" style="width: 190px; margin-bottom: 20px;" class="center-block">
</section>

2
purchase_recurring_orders/tests/__init__.py

@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
from . import test_recurring_orders

32
purchase_recurring_orders/tests/test_recurring_orders.py

@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
from openerp.tests import common
class TestRecurringOrder(common.TransactionCase):
def setUp(self):
super(TestRecurringOrder, self).setUp()
self.agreement_model = self.env['purchase.recurring_orders.agreement']
self.agreement = self.agreement_model.create(
{'name': 'Agreement test',
'partner_id': self.env.ref('base.res_partner_1').id})
self.line_model = self.env['purchase.recurring_orders.agreement.line']
self.agreement_line = self.line_model.create(
{'agreement_id': self.agreement.id,
'product_id': self.env.ref('product.product_product_1').id})
self.agreement.generate_next_year_orders()
def test_order_creation_next_year(self):
self.assertEqual(len(self.agreement.order_line), 12)
def test_order_creation_two_years(self):
self.agreement.generate_next_orders(years=2)
self.assertEqual(len(self.agreement.order_line), 24)
def test_order_cleanup_change(self):
self.agreement.active = False
self.assertEqual(len(self.agreement.order_line), 0)
def test_order_cleanup_change_with_confirmed_and_order_line(self):
self.agreement.order_line[0].action_button_confirm()
self.agreement.prolong_interval = 2
self.assertEqual(len(self.agreement.order_line), 1)

28
purchase_recurring_orders/views/purchase_order_view.xml

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="view_purchase_order_form" model="ir.ui.view">
<field name="name">purchase.order.form.recurring_orders_inherited</field>
<field name="model">purchase.order</field>
<field name="inherit_id" ref="purchase.purchase_order_form"/>
<field name="arch" type="xml">
<button name="action_cancel" position="after">
<button name="action_button_generate_agreement" string="Generate agreement" type="object"/>
</button>
</field>
</record>
<record id="view_purchases_order_filter" model="ir.ui.view">
<field name="name">purchase.order.form.list.select_inherited</field>
<field name="model">purchase.order</field>
<field name="inherit_id" ref="purchase.view_purchase_order_filter"/>
<field name="arch" type="xml">
<field name="name" position="after">
<filter string="Not from agreements" name="from_agreement" domain="[('from_agreement','=',False)]"/>
<separator/>
</field>
</field>
</record>
</data>
</openerp>

170
purchase_recurring_orders/views/recurring_orders_view.xml

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record model="ir.ui.view" id="view_purchase_recurring_orders_agreement_renewal_tree">
<field name="name">purchase.recurring_orders.agreement.renewal.tree</field>
<field name="model">purchase.recurring_orders.agreement.renewal</field>
<field name="type">tree</field>
<field name="priority" eval="6"/>
<field name="arch" type="xml">
<tree string="Agreement renewals">
<field name="date"/>
<field name="comments"/>
</tree>
</field>
</record>
<record model="ir.ui.view" id="view_purchase_recurring_orders_agreement_tree">
<field name="name">purchase.recurring_orders.agreement.tree</field>
<field name="model">purchase.recurring_orders.agreement</field>
<field name="type">tree</field>
<field name="priority" eval="6"/>
<field name="arch" type="xml">
<tree string="Recurring orders agreements">
<field name="number"/>
<field name="name"/>
<field name="partner_id"/>
<field name="start_date"/>
<field name="prolong"/>
<field name="next_expiration_date"/>
<field name="company_id" groups="base.group_multi_company"/>
</tree>
</field>
</record>
<record model="ir.ui.view" id="view_purchase_recurring_orders_agreement_form">
<field name="name">purchase.recurring_orders.agreement.form</field>
<field name="model">purchase.recurring_orders.agreement</field>
<field name="type">form</field>
<field name="priority" eval="6"/>
<field name="arch" type="xml">
<form string="Agreement" version="7.0">
<header>
<button name="generate_initial_order" string="Generate initial order" class="oe_highlight" type="object" states="empty"/>
<button name="generate_next_year_orders" string="Generate next year orders" type="object" icon="gtk-execute"/>
<button name="%(action_purchase_recurring_orders_renew_wizard)d" type="action" string="Renew agreement" attrs="{'invisible': [('prolong','!=', 'recurrent')]}"/>
</header>
<sheet>
<group>
<group>
<field name="name"/>
<field name="partner_id" domain="[('customer','=',True)]"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
<group>
<field name="active"/>
<field name="number"/>
<field name="start_date" on_change="onchange_start_date(start_date)" attrs="{'readonly':[('state','!=','empty')]}"/>
</group>
</group>
<group>
<group>
<field name="prolong" attrs="{'readonly':[('renewal_state','!=','not_renewed')]}"/>
<field name="next_expiration_date"/>
<field name="last_renovation_date" readonly="1" attrs="{'invisible': [('prolong','!=', 'recurrent')]}"/>
<field name="state" invisible="1"/>
<field name="renewal_state" invisible="1"/>
</group>
<group>
<group attrs="{'invisible': [('prolong','=', 'fixed')]}" colspan="4" col="4">
<field name="prolong_interval" attrs="{'required': [('prolong', '!=', 'fixed')]}"/>
<field name="prolong_unit" attrs="{'required': [('prolong', '!=', 'fixed')]}"/>
</group>
<group attrs="{'invisible': [('prolong','!=', 'fixed')]}" colspan="4" col="2">
<field name="end_date" attrs="{'required': [('prolong', '=', 'fixed')]}"/>
</group>
</group>
</group>
<notebook colspan="4">
<page string="Lines">
<field name="agreement_line">
<tree string="Agreement lines" editable="bottom">
<field name="active_chk"/>
<field name="product_id" on_change="onchange_product_id(product_id)"/>
<field name="additional_description"/>
<field name="quantity"/>
<field name="uom_id"/>
<field name="list_price"/>
<field name="specific_price"/>
<field name="discount"/>
<field name="ordering_interval"/>
<field name="ordering_unit"/>
<field name="last_order_date" readonly="True"/>
</tree>
</field>
</page>
<page string="Orders">
<field colspan="4" mode="tree" name="order_line" widget="one2many_list" nolabel="1">
<tree editable="bottom">
<button name="view_order"
string="View order"
type="object"
icon="terp-gtk-go-back-rtl"/>
<field name="name"/>
<field name="date_order"/>
<field name="state"/>
</tree>
</field>
</page>
<page string="Renewals" attrs="{'invisible': [('prolong','!=', 'recurrent')]}">
<field colspan="4" mode="tree" name="renewal_line" widget="one2many_list" nolabel="1"/>
</page>
</notebook>
<div class="oe_clear"/>
<field name="notes"/>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers" groups="base.group_user"/>
<field name="message_ids" widget="mail_thread" placeholder="Share a message..."/>
</div>
</form>
</field>
</record>
<record id="view_purchases_ro_agreement_filter" model="ir.ui.view">
<field name="name">purchase.recurring_orders.agreement.select</field>
<field name="model">purchase.recurring_orders.agreement</field>
<field name="arch" type="xml">
<search>
<field name="number" filter_domain="[('number', 'ilike', self)]"/>
<field name="name" string="Agreement name" filter_domain="[('name', 'ilike', self)]"/>
<field name="partner_id" filter_domain="[('partner_id', 'child_of', self)]"/>
</search>
</field>
</record>
<record model="ir.actions.act_window" id="action_purchase_recurring_orders_agreement">
<field name="name">Recurring orders agreements</field>
<field name="res_model">purchase.recurring_orders.agreement</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="search_view_id" ref="view_purchases_ro_agreement_filter"/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to set a new agreement.
</p><p>
Agreements are the way you define the commercial relation with your customers which specify certain products/services that you are providing them that requires a recurring order.
</p>
</field>
</record>
<record model="ir.actions.act_window.view" id="action_purchase_recurring_orders_agreement_view_tree">
<field name="sequence" eval="1"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="view_purchase_recurring_orders_agreement_tree"/>
<field name="act_window_id" ref="action_purchase_recurring_orders_agreement"/>
</record>
<record model="ir.actions.act_window.view" id="action_purchase_recurring_orders_agreement_view_form">
<field name="sequence" eval="2"/>
<field name="view_mode">form</field>
<field name="view_id" ref="view_purchase_recurring_orders_agreement_form"/>
<field name="act_window_id" ref="action_purchase_recurring_orders_agreement"/>
</record>
<menuitem name="Recurring orders agreements" id="menu_recurring_orders_agreements"
parent="purchase.menu_procurement_management" action="action_purchase_recurring_orders_agreement"
sequence="4"/>
</data>
</openerp>

33
purchase_recurring_orders/views/res_partner_view.xml

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="act_res_partner_2_agreement" model="ir.actions.act_window">
<field name="name">Agreements</field>
<field name="res_model">purchase.recurring_orders.agreement</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="context">{'search_default_partner_id': active_id}</field>
<field name="groups_id" eval="[(4, ref('base.group_sale_salesman'))]"/>
</record>
<record id="res_partner_view_agreement_button" model="ir.ui.view">
<field name="name">res.partner.view.agreement.button</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form" />
<field name="priority" eval="20"/>
<field name="arch" type="xml">
<xpath expr="//div[@name='buttons']" position="inside">
<button name="%(act_res_partner_2_agreement)d"
type="action"
class="oe_stat_button"
icon="fa-file-text-o"
string="Agreements"
attrs="{'invisible': [('customer', '=', False)]}"
groups="base.group_sale_salesman"/>
</xpath>
</field>
</record>
</data>
</openerp>

23
purchase_recurring_orders/wizard/__init__.py

@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
# Copyright (C) 2009-TODAY Cybrosys Technologies(<http://www.cybrosys.com>).
# Author: Jesni Banu(<http://www.cybrosys.com>)
# you can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# It is forbidden to publish, distribute, sublicense, or sell copies
# of the Software or modified copies of the Software.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# GENERAL PUBLIC LICENSE (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
###############################################################################
from . import renew_wizard

55
purchase_recurring_orders/wizard/renew_wizard.py

@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
# Copyright (C) 2009-TODAY Cybrosys Technologies(<http://www.cybrosys.com>).
# Author: Jesni Banu(<http://www.cybrosys.com>)
# you can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# It is forbidden to publish, distribute, sublicense, or sell copies
# of the Software or modified copies of the Software.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# GENERAL PUBLIC LICENSE (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
###############################################################################
from openerp import models, fields, api
class RenewWizard(models.TransientModel):
_name = "purchase.recurring_orders.renew_wizard"
def _get_renewal_date(self):
agreements = self.env['purchase.recurring_orders.agreement'].browse(
self.env.context.get('active_ids', []))
return agreements[:1].next_expiration_date
date = fields.Date(
string='Renewal date', required=True,
help="Effective date of the renewal. This date is the one taken into "
"account in the next renewal",
default=_get_renewal_date)
comments = fields.Char(
string='Comments', size=200, help='Renewal comments')
@api.multi
def create_renewal(self, cr, uid, ids, context=None):
self.ensure_one()
agreement_ids = context.get('active_ids', [])
for agreement_id in agreement_ids:
self.env['purchase.recurring_orders.agreement.renewal'].create(
{'agreement_id': agreement_id,
'date': self.date,
'comments': self.comments})
agreement_model = self.env['purchase.recurring_orders.agreement']
agreement_model.browse(agreement_ids).write(
{'last_renovation_date': self.date,
'renewal_state': 'renewed'})
return True

33
purchase_recurring_orders/wizard/renew_wizard_view.xml

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="view_purchase_recurring_orders_renew_wizard" model="ir.ui.view">
<field name="name">Renew agreement wizard</field>
<field name="model">purchase.recurring_orders.renew_wizard</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Renew agreement wizard">
<label string="Set renewal agreement data" colspan="4"/>
<newline/>
<field name="date"/>
<newline/>
<field name="comments" default_focus="1"/>
<separator string="" colspan="4" />
<button special="cancel" string="Cancel" icon="gtk-cancel"/>
<button name="create_renewal" string="Renew agreement" type="object" icon="gtk-ok"/>
</form>
</field>
</record>
<act_window name="Renew agreement wizard"
res_model="purchase.recurring_orders.renew_wizard"
src_model="purchase.recurring_orders.agreement"
view_mode="form"
target="new"
multi="True"
key2="client_action_multi"
id="action_purchase_recurring_orders_renew_wizard"/>
</data>
</openerp>
Loading…
Cancel
Save