From 83a34f56c9384cd7baf41934c0c43dd8a9091b52 Mon Sep 17 00:00:00 2001 From: "psi (Open ERP)" Date: Mon, 16 May 2011 15:16:55 +0530 Subject: [PATCH 1/8] [IMP] split payslip report in to two. Employee payslip report will show the payslip lines appears on payslip and the payslip details report will show all the payslip line by category as well as by contribution register bzr revid: psi@tinyerp.co.in-20110516094655-gj6qtu29b6us3lh2 --- addons/hr_payroll/hr_payroll_report.xml | 126 ++--- addons/hr_payroll/report/__init__.py | 3 +- addons/hr_payroll/report/report_payslip.py | 127 ------ addons/hr_payroll/report/report_payslip.rml | 309 ++++--------- .../report/report_payslip_details.py | 113 +++++ .../report/report_payslip_details.rml | 431 ++++++++++++++++++ 6 files changed, 695 insertions(+), 414 deletions(-) create mode 100644 addons/hr_payroll/report/report_payslip_details.py create mode 100644 addons/hr_payroll/report/report_payslip_details.rml diff --git a/addons/hr_payroll/hr_payroll_report.xml b/addons/hr_payroll/hr_payroll_report.xml index 23e72d511f0..5a6816da317 100644 --- a/addons/hr_payroll/hr_payroll_report.xml +++ b/addons/hr_payroll/hr_payroll_report.xml @@ -1,65 +1,73 @@ - - - - + + + - - - - - + - - - - - + + + diff --git a/addons/hr_payroll/report/__init__.py b/addons/hr_payroll/report/__init__.py index 3113f281326..e4d936e9197 100644 --- a/addons/hr_payroll/report/__init__.py +++ b/addons/hr_payroll/report/__init__.py @@ -3,7 +3,7 @@ ############################################################################## # -# OpenERP, Open Source Management Solution +# OpenERP, Open Source Management Solution # Copyright (C) 2004-2009 Tiny SPRL (). All Rights Reserved # d$ # @@ -23,6 +23,7 @@ ############################################################################## import report_payslip +import report_payslip_details import report_payroll_advice import report_year_salary import report_payroll_register diff --git a/addons/hr_payroll/report/report_payslip.py b/addons/hr_payroll/report/report_payslip.py index f1f658e40d2..f666e2f4606 100644 --- a/addons/hr_payroll/report/report_payslip.py +++ b/addons/hr_payroll/report/report_payslip.py @@ -22,7 +22,6 @@ # ############################################################################## -from datetime import datetime from report import report_sxw from tools import amount_to_text_en @@ -31,61 +30,9 @@ class payslip_report(report_sxw.rml_parse): def __init__(self, cr, uid, name, context): super(payslip_report, self).__init__(cr, uid, name, context) self.localcontext.update({ - 'convert': self.convert, - 'get_month': self.get_month, - 'get_earnings': self.get_earnings, - 'get_deductions':self.get_deductions, - 'get_leave': self.get_leave, 'get_payslip_lines': self.get_payslip_lines, - 'get_details_by_rule_category': self.get_details_by_rule_category, - 'get_lines_by_contribution_register': self.get_lines_by_contribution_register, }) - def convert(self, amount, cur): - amt_en = amount_to_text_en.amount_to_text(amount, 'en', cur) - return amt_en - - def get_leave(self, obj): - payslip_line = self.pool.get('hr.payslip.line') - res = [] -# ids = [] -# for id in range(len(obj)): -# if obj[id].type == 'leaves': -# ids.append(obj[id].id) -# if ids: -# res = payslip_line.browse(self.cr, self.uid, ids) - return res - - def get_earnings(self, obj): - payslip_line = self.pool.get('hr.payslip.line') - res = [] - ids = [] - for id in range(len(obj)): - if obj[id].category_id.parent_id.name == 'Allowance': - ids.append(obj[id].id) - if ids: - res = payslip_line.browse(self.cr, self.uid, ids) - return res - - def get_deductions(self, obj): - payslip_line = self.pool.get('hr.payslip.line') - res = [] - ids = [] - for id in range(len(obj)): - if obj[id].category_id.parent_id.name == 'Deduction': - ids.append(obj[id].id) - if ids: - res = payslip_line.browse(self.cr, self.uid, ids) - return res - - def get_month(self, obj): - res = { - 'mname':'' - } - date = datetime.strptime(obj.date, '%Y-%m-%d') - res['mname']= date.strftime('%B')+"-"+date.strftime('%Y') - return res['mname'] - def get_payslip_lines(self, obj): payslip_line = self.pool.get('hr.payslip.line') res = [] @@ -97,80 +44,6 @@ class payslip_report(report_sxw.rml_parse): res = payslip_line.browse(self.cr, self.uid, ids) return res - def get_recursive_parent(self, rule_categories): - if not rule_categories: - return [] - if rule_categories[0].parent_id: - rule_categories.insert(0, rule_categories[0].parent_id) - self.get_recursive_parent(rule_categories) - return rule_categories - - def get_details_by_rule_category(self, obj): - payslip_line = self.pool.get('hr.payslip.line') - rule_cate_obj = self.pool.get('hr.salary.rule.category') - res = [] - result = {} - ids = [] - - for id in range(len(obj)): - ids.append(obj[id].id) - if ids: - self.cr.execute('''SELECT pl.id, pl.category_id FROM hr_payslip_line as pl \ - LEFT JOIN hr_salary_rule_category AS rc on (pl.category_id = rc.id) \ - WHERE pl.id in %s \ - GROUP BY rc.parent_id, pl.sequence, pl.id, pl.category_id \ - ORDER BY pl.sequence, rc.parent_id''',(tuple(ids),)) - for x in self.cr.fetchall(): - result.setdefault(x[1], []) - result[x[1]].append(x[0]) - for key, value in result.iteritems(): - rule_categories = rule_cate_obj.browse(self.cr, self.uid, [key]) - parents = self.get_recursive_parent(rule_categories) - category_total = 0 - for line in payslip_line.browse(self.cr, self.uid, value): - category_total += line.total - level = 0 - for parent in parents: - res.append({ - 'rule_category': parent.name, - 'name': parent.name, - 'code': parent.code, - 'level': level, - 'total': category_total, - }) - level += 1 - for line in payslip_line.browse(self.cr, self.uid, value): - res.append({ - 'rule_category': line.name, - 'name': line.name, - 'code': line.code, - 'total': line.total, - 'level': level - }) - return res - - def get_lines_by_contribution_register(self, obj): - payslip_line = self.pool.get('hr.payslip.line') - result = {} - res = [] - - for id in range(len(obj)): - if obj[id].register_id: - result.setdefault(obj[id].register_id.name, []) - result[obj[id].register_id.name].append(obj[id].id) - for key, value in result.iteritems(): - res.append({ - 'register_name': key, - }) - for line in payslip_line.browse(self.cr, self.uid, value): - res.append({ - 'name': line.name, - 'code': line.code, - 'total': line.total, - }) - return res - -#report_sxw.report_sxw('report.payslip.pdf', 'hr.payslip', 'hr_payroll/report/payslip.rml', parser=payslip_report) report_sxw.report_sxw('report.test.pdf', 'hr.payslip', 'hr_payroll/report/report_payslip.rml', parser=payslip_report) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: \ No newline at end of file diff --git a/addons/hr_payroll/report/report_payslip.rml b/addons/hr_payroll/report/report_payslip.rml index 02c97e1ec62..2cc45847d0a 100644 --- a/addons/hr_payroll/report/report_payslip.rml +++ b/addons/hr_payroll/report/report_payslip.rml @@ -114,67 +114,9 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + @@ -184,36 +126,28 @@ - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + @@ -229,7 +163,6 @@ - @@ -239,11 +172,11 @@ - [[repeatIn(objects,'o')]] + [[repeatIn(objects,'o')]] - Pay Slip + Pay Slip @@ -253,20 +186,20 @@ Note - ([[o.name]]) + ([[o.name]]) - Name + Name - [[o.employee_id.name]] + [[o.employee_id.name]] - Designation + Designation - [[ o.employee_id.job_id.name or '' ]] + [[ o.employee_id.job_id.name or '' ]] @@ -278,17 +211,17 @@ - [[o.employee_id.address_home_id and o.employee_id.address_home_id.name or '' ]],[[o.employee_id.address_home_id and o.employee_id.address_home_id.street or '' ]],[[o.employee_id.address_home_id and o.employee_id.address_home_id.street2 or '' ]],[[o.employee_id.address_home_id and o.employee_id.address_home_id.zip or '' ]],[[o.employee_id.address_home_id and o.employee_id.address_home_id.city or '' ]],[[o.employee_id.address_home_id and o.employee_id.address_home_id.state_id and o.employee_id.address_home_id.state_id.name or '' ]] [[o.employee_id.address_home_id and o.employee_id.address_home_id.country_id and o.employee_id.address_home_id.country_id.name or '' ]] + [[o.employee_id.address_home_id and o.employee_id.address_home_id.name or '' ]],[[o.employee_id.address_home_id and o.employee_id.address_home_id.street or '' ]],[[o.employee_id.address_home_id and o.employee_id.address_home_id.street2 or '' ]],[[o.employee_id.address_home_id and o.employee_id.address_home_id.zip or '' ]],[[o.employee_id.address_home_id and o.employee_id.address_home_id.city or '' ]],[[o.employee_id.address_home_id and o.employee_id.address_home_id.state_id and o.employee_id.address_home_id.state_id.name or '' ]] [[o.employee_id.address_home_id and o.employee_id.address_home_id.country_id and o.employee_id.address_home_id.country_id.name or '' ]] - Email + Email - [[ o.employee_id.work_email or '' ]] + [[ o.employee_id.work_email or '' ]] @@ -296,33 +229,33 @@ - [[ o.employee_id.identification_id or '' ]] + [[ o.employee_id.identification_id or '' ]] - Reference + Reference - [[ o.number or '' ]] + [[ o.number or '' ]] - Bank Account + Bank Account - [[ o.employee_id.otherid or '' ]] + [[ o.employee_id.otherid or '' ]] - Date From + Date From - [[ o.date_from or '']] + [[ o.date_from or '']] @@ -330,168 +263,90 @@ - [[ o.date_to or '' ]] + [[ o.date_to or '' ]] - + - + - Payslip Lines: + Payslip Lines: - Code + Code - Name + Name - Amount (in [[o.company_id and o.company_id.currency_id.symbol or '']]) + Amount (in [[o.company_id and o.company_id.currency_id.symbol or '']]) -
- [[repeatIn(get_payslip_lines(o.line_ids),'p') ]] - - - - [[ p.code ]] - - - [[ p.name ]] - - - [[ p.total or '0.0']] - - - -
- - - - Details by Salary Rule Category: - - - - - - - Code - - - Salary Rule Category - - - Name - - - Amount(in [[o.company_id and o.company_id.currency_id.symbol or '']]) - - - +
+ [[repeatIn(get_payslip_lines(o.line_ids),'p') ]] + + + + [[ p.code ]] + + + [[ p.name ]] + + + [[ p.total or '0.0']] + + + + + + +
+ + + + + + + + + -
- [[repeatIn(get_details_by_rule_category(o.details_by_salary_rule_category),'h') ]] - - - - - [[ h['code'] ]] - [[ h['level']!=0 and ( setTag('para','para',{'style':'terp_default_8'})) or removeParentNode('font')]] - - - - [[ '..'*h['level'] ]][[ h['rule_category'] ]][[ h['level']!=0 and ( setTag('para','para',{'style':'terp_default_8'})) or removeParentNode('font') ]] - - - [[ h['name'] ]][[ h['level']!=0 and ( setTag('para','para',{'style':'terp_default_8'})) or removeParentNode('font')]] - - - [[ h['total'] ]] [[ h['level']!=0 and ( setTag('para','para',{'style':'terp_default_10'})) or removeParentNode('font') ]] - - - -
- - - - - - - Payslip Lines by Contribution Register: - - - - - - - Register Name - - - Code - - - Name - - - Amount (in [[o.company_id and o.company_id.currency_id.symbol or '']]) - - - -
- [[repeatIn(get_lines_by_contribution_register(o.details_by_salary_rule_category),'r') ]] - - - - [[ r.get('register_name', False) ]][[ h.get('register_name', False) and ( setTag('para','para',{'style':'terp_default_8'})) or removeParentNode('font')]] - - - [[ r['code'] ]] - - - [[ r['name'] ]] - - - [[ r['total'] or '0.0']] - - - -
- + - + - + - + - Authorized Signature + Authorized Signature - +
diff --git a/addons/hr_payroll/report/report_payslip_details.py b/addons/hr_payroll/report/report_payslip_details.py new file mode 100644 index 00000000000..6bc737a385f --- /dev/null +++ b/addons/hr_payroll/report/report_payslip_details.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python +#-*- coding:utf-8 -*- + +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2009 Tiny SPRL (). All Rights Reserved +# d$ +# +# 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 . +# +############################################################################## + +from report import report_sxw +from tools import amount_to_text_en + +class payslip_details_report(report_sxw.rml_parse): + + def __init__(self, cr, uid, name, context): + super(payslip_details_report, self).__init__(cr, uid, name, context) + self.localcontext.update({ + 'get_details_by_rule_category': self.get_details_by_rule_category, + 'get_lines_by_contribution_register': self.get_lines_by_contribution_register, + }) + + def get_details_by_rule_category(self, obj): + payslip_line = self.pool.get('hr.payslip.line') + rule_cate_obj = self.pool.get('hr.salary.rule.category') + + def get_recursive_parent(rule_categories): + if not rule_categories: + return [] + if rule_categories[0].parent_id: + rule_categories.insert(0, rule_categories[0].parent_id) + self.get_recursive_parent(rule_categories) + return rule_categories + + res = [] + result = {} + ids = [] + + for id in range(len(obj)): + ids.append(obj[id].id) + if ids: + self.cr.execute('''SELECT pl.id, pl.category_id FROM hr_payslip_line as pl \ + LEFT JOIN hr_salary_rule_category AS rc on (pl.category_id = rc.id) \ + WHERE pl.id in %s \ + GROUP BY rc.parent_id, pl.sequence, pl.id, pl.category_id \ + ORDER BY pl.sequence, rc.parent_id''',(tuple(ids),)) + for x in self.cr.fetchall(): + result.setdefault(x[1], []) + result[x[1]].append(x[0]) + for key, value in result.iteritems(): + rule_categories = rule_cate_obj.browse(self.cr, self.uid, [key]) + parents = get_recursive_parent(rule_categories) + category_total = 0 + for line in payslip_line.browse(self.cr, self.uid, value): + category_total += line.total + level = 0 + for parent in parents: + res.append({ + 'rule_category': parent.name, + 'name': parent.name, + 'code': parent.code, + 'level': level, + 'total': category_total, + }) + level += 1 + for line in payslip_line.browse(self.cr, self.uid, value): + res.append({ + 'rule_category': line.name, + 'name': line.name, + 'code': line.code, + 'total': line.total, + 'level': level + }) + return res + + def get_lines_by_contribution_register(self, obj): + payslip_line = self.pool.get('hr.payslip.line') + result = {} + res = [] + + for id in range(len(obj)): + if obj[id].register_id: + result.setdefault(obj[id].register_id.name, []) + result[obj[id].register_id.name].append(obj[id].id) + for key, value in result.iteritems(): + res.append({ + 'register_name': key, + }) + for line in payslip_line.browse(self.cr, self.uid, value): + res.append({ + 'name': line.name, + 'code': line.code, + 'total': line.total, + }) + return res + +report_sxw.report_sxw('report.paylip.details', 'hr.payslip', 'hr_payroll/report/report_payslip_details.rml', parser=payslip_details_report) + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: \ No newline at end of file diff --git a/addons/hr_payroll/report/report_payslip_details.rml b/addons/hr_payroll/report/report_payslip_details.rml new file mode 100644 index 00000000000..132e793cb3a --- /dev/null +++ b/addons/hr_payroll/report/report_payslip_details.rml @@ -0,0 +1,431 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [[repeatIn(objects,'o')]] + + + + Pay Slip Details + + + + + [[o.credit_note==False and removeParentNode('para')]] + Credit + + Note + + ([[o.name]]) + + + + Name + + + [[o.employee_id.name]] + + + Designation + + + [[ o.employee_id.job_id.name or '' ]] + + + + + + + + Address + + + + [[o.employee_id.address_home_id and o.employee_id.address_home_id.name or '' ]],[[o.employee_id.address_home_id and o.employee_id.address_home_id.street or '' ]],[[o.employee_id.address_home_id and o.employee_id.address_home_id.street2 or '' ]],[[o.employee_id.address_home_id and o.employee_id.address_home_id.zip or '' ]],[[o.employee_id.address_home_id and o.employee_id.address_home_id.city or '' ]],[[o.employee_id.address_home_id and o.employee_id.address_home_id.state_id and o.employee_id.address_home_id.state_id.name or '' ]] [[o.employee_id.address_home_id and o.employee_id.address_home_id.country_id and o.employee_id.address_home_id.country_id.name or '' ]] + + + + + + + Email + + + [[ o.employee_id.work_email or '' ]] + + + + Identification No + + + + [[ o.employee_id.identification_id or '' ]] + + + + + + + Reference + + + [[ o.number or '' ]] + + + Bank Account + + + [[ o.employee_id.otherid or '' ]] + + + + + + + Date From + + + [[ o.date_from or '']] + + + + Date To + + + + [[ o.date_to or '' ]] + + + + + + + + + + + + + + Details by Salary Rule Category: + + + + + + + Code + + + Salary Rule Category + + + Name + + + Amount(in [[o.company_id and o.company_id.currency_id.symbol or '']]) + + + + + + +
+ [[repeatIn(get_details_by_rule_category(o.details_by_salary_rule_category),'h') ]] + + + + + [[ h['code'] ]] + [[ h['level']!=0 and ( setTag('para','para',{'style':'terp_default_8'})) or removeParentNode('font')]] + + + + [[ '..'*h['level'] ]][[ h['rule_category'] ]][[ h['level']!=0 and ( setTag('para','para',{'style':'terp_default_8'})) or removeParentNode('font') ]] + + + [[ h['name'] ]][[ h['level']!=0 and ( setTag('para','para',{'style':'terp_default_8'})) or removeParentNode('font')]] + + + [[ h['total'] ]] [[ h['level']!=0 and ( setTag('para','para',{'style':'terp_default_10'})) or removeParentNode('font') ]] + + + +
+ + + + + + + + Payslip Lines by Contribution Register: + + + + + + + Register Name + + + Code + + + Name + + + Amount (in [[o.company_id and o.company_id.currency_id.symbol or '']]) + + + +
+ [[repeatIn(get_lines_by_contribution_register(o.details_by_salary_rule_category),'r')]] + + + + [[ r.get('register_name', False) ]][[ h.get('register_name', False) and ( setTag('para','para',{'style':'terp_default_8'})) or removeParentNode('font')]] + + + [[ r['code'] ]] + + + [[ r['name'] ]] + + + [[ r['total'] or '0.0']] + + + +
+ + + + + + + + + + + + + + + + + + Authorized Signature + + + + + + +
+
+ From 0df1f0680159cb8713ba4b194664076cf171bed5 Mon Sep 17 00:00:00 2001 From: mtr Date: Tue, 17 May 2011 16:21:55 +0530 Subject: [PATCH 2/8] [IMP] hr_payroll: improved payslip states(draft, verify, done) and workflow bzr revid: mtr@mtr-20110517105155-d4dp2tt7d7ut4jxd --- addons/hr_payroll/hr_payroll.py | 27 +++++--- addons/hr_payroll/hr_payroll_view.xml | 7 +- addons/hr_payroll/hr_payroll_workflow.xml | 81 ++++++++++++++--------- 3 files changed, 72 insertions(+), 43 deletions(-) diff --git a/addons/hr_payroll/hr_payroll.py b/addons/hr_payroll/hr_payroll.py index b70b30676a8..9ac05332801 100644 --- a/addons/hr_payroll/hr_payroll.py +++ b/addons/hr_payroll/hr_payroll.py @@ -255,12 +255,16 @@ class hr_payslip(osv.osv): 'date_from': fields.date('Date From', readonly=True, states={'draft': [('readonly', False)]}, required=True), 'date_to': fields.date('Date To', readonly=True, states={'draft': [('readonly', False)]}, required=True), 'state': fields.selection([ - ('draft', 'Waiting for Verification'), - ('hr_check', 'Waiting for HR Verification'), - ('accont_check', 'Waiting for Account Verification'), - ('confirm', 'Confirm Sheet'), - ('done', 'Paid Salary'), + ('draft', 'Draft'), + ('verify', 'Verify'), + ('done', 'Done'), ('cancel', 'Reject'), +# ('draft', 'Waiting for Verification'), +# ('hr_check', 'Waiting for HR Verification'), +# ('accont_check', 'Waiting for Account Verification'), +# ('confirm', 'Confirm Sheet'), +# ('done', 'Paid Salary'), +# ('cancel', 'Reject'), ], 'State', select=True, readonly=True, help=' * When the payslip is created the state is \'Waiting for verification\'.\ \n* It is varified by the user and payslip is sent for HR varification, the state is \'Waiting for HR Verification\'. \ @@ -310,21 +314,23 @@ class hr_payslip(osv.osv): def account_check_sheet(self, cr, uid, ids, context=None): return self.write(cr, uid, ids, {'state': 'accont_check'}, context=context) - + def hr_check_sheet(self, cr, uid, ids, context=None): return self.write(cr, uid, ids, {'state': 'hr_check'}, context=context) - + def process_sheet(self, cr, uid, ids, context=None): return self.write(cr, uid, ids, {'paid': True, 'state': 'done'}, context=context) + def hr_verify_sheet(self, cr, uid, ids, context=None): + return self.write(cr, uid, ids, {'state': 'verify'}, context=context) + def refund_sheet(self, cr, uid, ids, context=None): mod_obj = self.pool.get('ir.model.data') wf_service = netsvc.LocalService("workflow") for id in ids: id_copy = self.copy(cr, uid, id, {'credit_note': True}, context=context) self.compute_sheet(cr, uid, [id_copy], context=context) - wf_service.trg_validate(uid, 'hr.payslip', id_copy, 'verify_sheet', cr) - wf_service.trg_validate(uid, 'hr.payslip', id_copy, 'final_verify_sheet', cr) + wf_service.trg_validate(uid, 'hr.payslip', id_copy, 'hr_verify_sheet', cr) wf_service.trg_validate(uid, 'hr.payslip', id_copy, 'process_sheet', cr) form_id = mod_obj.get_object_reference(cr, uid, 'hr_payroll', 'view_hr_payslip_form') @@ -347,6 +353,9 @@ class hr_payslip(osv.osv): def verify_sheet(self, cr, uid, ids, context=None): return self.write(cr, uid, ids, {'state': 'confirm'}, context=context) + + def check(self, cr, uid, ids, context=None): + return True #TODO move this function into hr_contract module, on hr.employee object def get_contract(self, cr, uid, employee, date_from, date_to, context=None): diff --git a/addons/hr_payroll/hr_payroll_view.xml b/addons/hr_payroll/hr_payroll_view.xml index 4d422d708de..4393f25b9d9 100644 --- a/addons/hr_payroll/hr_payroll_view.xml +++ b/addons/hr_payroll/hr_payroll_view.xml @@ -329,13 +329,14 @@ -