From 83121d3b61ea1c1458416f29e0dd5761ec3ba2bc Mon Sep 17 00:00:00 2001 From: Stefan Katzensteiner Date: Mon, 4 May 2015 13:33:53 +0200 Subject: [PATCH] Added modules sale_order_optional and sale_order_reminder --- .../sale_order_optional/__init__.py | 19 +++ .../sale_order_optional/__openerp__.py | 39 +++++ .../sale_order_optional.py | 135 ++++++++++++++++++ .../sale_order_optional_data.xml | 6 + .../sale_order_optional_view.xml | 17 +++ .../static/description/icon.png | Bin 0 -> 2870 bytes .../sale_order_reminder/__init__.py | 19 +++ .../sale_order_reminder/__openerp__.py | 41 ++++++ .../sale_order_reminder/email_template.xml | 46 ++++++ .../sale_order_reminder.py | 68 +++++++++ .../sale_order_reminder_data.xml | 16 +++ .../sale_order_reminder_view.xml | 31 ++++ .../static/description/icon.png | Bin 0 -> 2870 bytes setup/lib/config_at.py | 2 + 14 files changed, 439 insertions(+) create mode 100755 ext/custom-addons/sale_order_optional/__init__.py create mode 100755 ext/custom-addons/sale_order_optional/__openerp__.py create mode 100644 ext/custom-addons/sale_order_optional/sale_order_optional.py create mode 100644 ext/custom-addons/sale_order_optional/sale_order_optional_data.xml create mode 100644 ext/custom-addons/sale_order_optional/sale_order_optional_view.xml create mode 100644 ext/custom-addons/sale_order_optional/static/description/icon.png create mode 100755 ext/custom-addons/sale_order_reminder/__init__.py create mode 100755 ext/custom-addons/sale_order_reminder/__openerp__.py create mode 100644 ext/custom-addons/sale_order_reminder/email_template.xml create mode 100644 ext/custom-addons/sale_order_reminder/sale_order_reminder.py create mode 100644 ext/custom-addons/sale_order_reminder/sale_order_reminder_data.xml create mode 100644 ext/custom-addons/sale_order_reminder/sale_order_reminder_view.xml create mode 100644 ext/custom-addons/sale_order_reminder/static/description/icon.png diff --git a/ext/custom-addons/sale_order_optional/__init__.py b/ext/custom-addons/sale_order_optional/__init__.py new file mode 100755 index 00000000..f003963c --- /dev/null +++ b/ext/custom-addons/sale_order_optional/__init__.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +import sale_order_optional diff --git a/ext/custom-addons/sale_order_optional/__openerp__.py b/ext/custom-addons/sale_order_optional/__openerp__.py new file mode 100755 index 00000000..b364af2e --- /dev/null +++ b/ext/custom-addons/sale_order_optional/__openerp__.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +{ + 'name': "Optional Lines for Sale Orders", + 'version': "1.0", + 'category': "Sales", + 'description': """ + This addon adds optional sale order lines. + """, + 'author': "Camadeus GmbH", + 'website': "http://www.camadeus.at", + 'css': [], + 'images': [], + 'depends': ['sale'], + 'data': ['sale_order_optional_view.xml', + 'sale_order_optional_data.xml', + ], + 'installable': True, + 'auto_install': False, + 'application': False, +} + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/ext/custom-addons/sale_order_optional/sale_order_optional.py b/ext/custom-addons/sale_order_optional/sale_order_optional.py new file mode 100644 index 00000000..f1db367a --- /dev/null +++ b/ext/custom-addons/sale_order_optional/sale_order_optional.py @@ -0,0 +1,135 @@ +# -*- encoding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2009 Tiny SPRL (). All Rights Reserved +# $Id$ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +############################################################################## + +from openerp.osv import fields, osv +import openerp.addons.decimal_precision as dp + +class sale_order(osv.osv): + _inherit = "sale.order" + + def _amount_line_tax(self, cr, uid, line, context=None): + val = 0.0 + if not line.optional: + for c in self.pool.get('account.tax').compute_all(cr, uid, line.tax_id, line.price_unit * (1-(line.discount or 0.0)/100.0), line.product_uom_qty, line.product_id, line.order_id.partner_id)['taxes']: + val += c.get('amount', 0.0) + return val + + def _amount_all(self, cr, uid, ids, field_name, arg, context=None): + cur_obj = self.pool.get('res.currency') + res = {} + for order in self.browse(cr, uid, ids, context=context): + res[order.id] = { + 'amount_untaxed': 0.0, + 'amount_tax': 0.0, + 'amount_total': 0.0, + } + val = val1 = 0.0 + cur = order.pricelist_id.currency_id + for line in order.order_line: + val1 += line.price_subtotal + val += self._amount_line_tax(cr, uid, line, context=context) + res[order.id]['amount_tax'] = cur_obj.round(cr, uid, cur, val) + res[order.id]['amount_untaxed'] = cur_obj.round(cr, uid, cur, val1) + res[order.id]['amount_total'] = res[order.id]['amount_untaxed'] + res[order.id]['amount_tax'] + return res + + def _amount_all_wrapper(self, cr, uid, ids, field_name, arg, context=None): + """ Wrapper because of direct method passing as parameter for function fields """ + return self._amount_all(cr, uid, ids, field_name, arg, context=context) + + def _get_order(self, cr, uid, ids, context=None): + result = {} + for line in self.pool.get('sale.order.line').browse(cr, uid, ids, context=context): + result[line.order_id.id] = True + return result.keys() + + _columns = { + 'amount_untaxed': fields.function(_amount_all_wrapper, digits_compute=dp.get_precision('Account'), string='Untaxed Amount', + store={ + 'sale.order': (lambda self, cr, uid, ids, c={}: ids, ['order_line'], 10), + 'sale.order.line': (_get_order, ['price_unit', 'tax_id', 'discount', 'product_uom_qty'], 10), + }, + multi='sums', help="The amount without tax.", track_visibility='always'), + 'amount_tax': fields.function(_amount_all_wrapper, digits_compute=dp.get_precision('Account'), string='Taxes', + store={ + 'sale.order': (lambda self, cr, uid, ids, c={}: ids, ['order_line'], 10), + 'sale.order.line': (_get_order, ['price_unit', 'tax_id', 'discount', 'product_uom_qty'], 10), + }, + multi='sums', help="The tax amount."), + 'amount_total': fields.function(_amount_all_wrapper, digits_compute=dp.get_precision('Account'), string='Total', + store={ + 'sale.order': (lambda self, cr, uid, ids, c={}: ids, ['order_line'], 10), + 'sale.order.line': (_get_order, ['price_unit', 'tax_id', 'discount', 'product_uom_qty'], 10), + }, + multi='sums', help="The total amount."), + } + +class sale_order_line(osv.osv): + _inherit = 'sale.order.line' + + def _fnct_line_invoiced(self, cr, uid, ids, field_name, args, context=None): + res = dict.fromkeys(ids, False) + for this in self.browse(cr, uid, ids, context=context): + res[this.id] = this.invoice_lines and \ + all(iline.invoice_id.state != 'cancel' for iline in this.invoice_lines) or \ + this.optional + return res + + def _order_lines_from_invoice(self, cr, uid, ids, context=None): + # direct access to the m2m table is the less convoluted way to achieve this (and is ok ACL-wise) + cr.execute("""SELECT DISTINCT sol.id FROM sale_order_invoice_rel rel JOIN + sale_order_line sol ON (sol.order_id = rel.order_id) + WHERE rel.invoice_id = ANY(%s)""", (list(ids),)) + return [i[0] for i in cr.fetchall()] + + def _amount_line(self, cr, uid, ids, field_name, arg, context=None): + tax_obj = self.pool.get('account.tax') + cur_obj = self.pool.get('res.currency') + res = {} + if context is None: + context = {} + for line in self.browse(cr, uid, ids, context=context): + if not line.optional: + price = line.price_unit * (1 - (line.discount or 0.0) / 100.0) + taxes = tax_obj.compute_all(cr, uid, line.tax_id, price, line.product_uom_qty, line.product_id, line.order_id.partner_id) + cur = line.order_id.pricelist_id.currency_id + res[line.id] = cur_obj.round(cr, uid, cur, taxes['total']) + else: + res[line.id] = 0 + return res + + _columns = { + 'invoiced': fields.function(_fnct_line_invoiced, string='Invoiced', type='boolean', + store={ + 'account.invoice': (_order_lines_from_invoice, ['state'], 10), + 'sale.order.line': (lambda self,cr,uid,ids,ctx=None: ids, ['invoice_lines'], 10) + }), + 'price_subtotal': fields.function(_amount_line, string='Subtotal', digits_compute= dp.get_precision('Account')), + 'optional': fields.boolean('Optional'), + } + + def _prepare_order_line_invoice_line(self, cr, uid, line, account_id=False, context=None): + if not line.optional: + return super(sale_order_line, self)._prepare_order_line_invoice_line(cr, uid, line, account_id, context=context) + else: + return False + \ No newline at end of file diff --git a/ext/custom-addons/sale_order_optional/sale_order_optional_data.xml b/ext/custom-addons/sale_order_optional/sale_order_optional_data.xml new file mode 100644 index 00000000..d13a406f --- /dev/null +++ b/ext/custom-addons/sale_order_optional/sale_order_optional_data.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/ext/custom-addons/sale_order_optional/sale_order_optional_view.xml b/ext/custom-addons/sale_order_optional/sale_order_optional_view.xml new file mode 100644 index 00000000..66056673 --- /dev/null +++ b/ext/custom-addons/sale_order_optional/sale_order_optional_view.xml @@ -0,0 +1,17 @@ + + + + + + sale.order.form.optional + sale.order + + + + + + + + + + diff --git a/ext/custom-addons/sale_order_optional/static/description/icon.png b/ext/custom-addons/sale_order_optional/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..fd7ee3b90ede569d07369b51f5036c9c0de4e9b2 GIT binary patch literal 2870 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. +# +############################################################################## + +import sale_order_reminder diff --git a/ext/custom-addons/sale_order_reminder/__openerp__.py b/ext/custom-addons/sale_order_reminder/__openerp__.py new file mode 100755 index 00000000..bbf29c4d --- /dev/null +++ b/ext/custom-addons/sale_order_reminder/__openerp__.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +{ + 'name': "Reminder for Sale Orders", + 'version': "1.0", + 'category': "Sales", + 'description': """ + This addon adds the field 'offer_valid_until' in the sale order. If this date is passed and the order is still in state 'draft' or 'sent', then + a reminder email is sent to the sales person every day. + """, + 'author': "Camadeus GmbH", + 'website': "http://www.camadeus.at", + 'css': [], + 'images': [], + 'depends': ['sale'], + 'data': ['sale_order_reminder_view.xml', + 'sale_order_reminder_data.xml', + 'email_template.xml', + ], + 'installable': True, + 'auto_install': False, + 'application': False, +} + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/ext/custom-addons/sale_order_reminder/email_template.xml b/ext/custom-addons/sale_order_reminder/email_template.xml new file mode 100644 index 00000000..512bc06f --- /dev/null +++ b/ext/custom-addons/sale_order_reminder/email_template.xml @@ -0,0 +1,46 @@ + + + + + + + + + Erinnerung: Angebot + ${(object.user_id and object.user_id.email or '')|safe} + Erinnerung: Angebot ${object.name} + ${object.user_id and object.user_id.partner_id.id} + + + +

Erinnerung für folgendes offenes Angebot:

+ +

+ + + + + + + + + + + + + + + + + +
Name:${object.name}
Datum: ${object.date_order}
Kunde: ${object.partner_id.display_name}
Auftragsvolumen (netto): ${object.amount_untaxed}
+ +

+
+ +]]>
+
+
+
diff --git a/ext/custom-addons/sale_order_reminder/sale_order_reminder.py b/ext/custom-addons/sale_order_reminder/sale_order_reminder.py new file mode 100644 index 00000000..a10e1d14 --- /dev/null +++ b/ext/custom-addons/sale_order_reminder/sale_order_reminder.py @@ -0,0 +1,68 @@ +# -*- encoding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2009 Tiny SPRL (). All Rights Reserved +# $Id$ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +############################################################################## + +from openerp import models, fields, api, _ +from datetime import datetime, timedelta +from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as DATE_FORMAT + +class res_company(models.Model): + _inherit = 'res.company' + + sale_offer_days = fields.Integer(string='Tage Gültigkeit Angebot', default=14) + +class sale_order(models.Model): + _inherit = 'sale.order' + + @api.model + def _offer_valid_until(self): + user = self.env['res.users'].browse(self._uid) + + now = datetime.now() + res = now + timedelta(days=user.company_id.sale_offer_days) + return res.strftime(DATE_FORMAT) + + + offer_valid_until = fields.Date(string='Angebot gültig bis', index=True, + readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, + default=_offer_valid_until) + + @api.model + def order_reminder_job(self): + today = fields.Date.today() + orders = self.search([('offer_valid_until','<',today),('state','in',('draft','sent')),('user_id','!=',False)]) + template_id = self.env['ir.model.data'].xmlid_to_res_id('sale_order_reminder.email_template_sale_reminder') + context = {'append_link': True} + for order in orders: + res = self.pool.get('email.template').send_mail(self._cr, self._uid, template_id, order.id, True, context=context) + return True + +class mail_mail(models.Model): + _inherit = 'mail.mail' + + def create(self, cr, uid, vals, context=None): + # notification field: set if it comes from order reminder job + if context is None: + context = {} + + if context.get('append_link'): + vals['notification'] = True + return super(mail_mail, self).create(cr, uid, vals, context=context) \ No newline at end of file diff --git a/ext/custom-addons/sale_order_reminder/sale_order_reminder_data.xml b/ext/custom-addons/sale_order_reminder/sale_order_reminder_data.xml new file mode 100644 index 00000000..044a3708 --- /dev/null +++ b/ext/custom-addons/sale_order_reminder/sale_order_reminder_data.xml @@ -0,0 +1,16 @@ + + + + + + Reminder for Sale Orders + 1 + days + -1 + + + + + + + diff --git a/ext/custom-addons/sale_order_reminder/sale_order_reminder_view.xml b/ext/custom-addons/sale_order_reminder/sale_order_reminder_view.xml new file mode 100644 index 00000000..589e3c62 --- /dev/null +++ b/ext/custom-addons/sale_order_reminder/sale_order_reminder_view.xml @@ -0,0 +1,31 @@ + + + + + + sale_order_valid_until_form + sale.order + + + + + + + 20 + # + + + "res_company_order_days_form" + res.company + + + + + + + + + + + + diff --git a/ext/custom-addons/sale_order_reminder/static/description/icon.png b/ext/custom-addons/sale_order_reminder/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..fd7ee3b90ede569d07369b51f5036c9c0de4e9b2 GIT binary patch literal 2870 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