diff --git a/library_management/__openerp__.py b/library_management/__openerp__.py index 5623f3115..4bf2d23da 100644 --- a/library_management/__openerp__.py +++ b/library_management/__openerp__.py @@ -1,7 +1,8 @@ { 'name': 'Public Library Management ', 'version': '1.0', - 'author' : 'Cybrosys', + 'author': 'Cybrosys Techno Solutions', + 'company': 'Cybrosys Techno Solutions', 'category': 'Tools', 'summary': 'book management', 'description': """ diff --git a/med_marketing/__init__.py b/med_marketing/__init__.py new file mode 100644 index 000000000..2c4eac3f8 --- /dev/null +++ b/med_marketing/__init__.py @@ -0,0 +1 @@ +import models \ No newline at end of file diff --git a/med_marketing/__openerp__.py b/med_marketing/__openerp__.py new file mode 100644 index 000000000..de7b2c85b --- /dev/null +++ b/med_marketing/__openerp__.py @@ -0,0 +1,25 @@ +{ + 'name': "Pharmacy Marketing", + 'summary': """Sales Representative, Doctors, Targets,..etc for Medicine filed """, + 'description': """ +""", + + 'author': 'Cybrosys Techno Solutions', + 'company': 'Cybrosys Techno Solutions', + 'category': 'Medicine', + 'version': '0.1', + 'depends': ["base", + 'medical_feature', + "sale", "marketing", + "marketing_campaign", + "hr", "account", + "hr_payroll"], + 'data': ['views/med_marketing_view.xml', + 'views/rep_view.xml', + 'views/doctor_view.xml', + 'views/doctor_exp_view.xml', ], + 'demo': ['demo/demo.xml'], + 'installable': True, + 'auto_install': False, + 'application': True, +} diff --git a/med_marketing/demo/demo.xml b/med_marketing/demo/demo.xml new file mode 100644 index 000000000..0995fe24b --- /dev/null +++ b/med_marketing/demo/demo.xml @@ -0,0 +1,138 @@ + + + + + + Neurology + + + Cardiology + + + Dentistry + + + ENT + + + Gynaecology + + + Pediatrics + + + + + + Liam Gomer + 1 + 150000 + + + + Roger Scott + 1 + 450000 + + + + + David + 1 + 850000 + + + + + Macklin + 1 + 780000 + + + + + Jack + 1 + 750000 + + + + Joe + 1 + 960000 + + + + + Dr. Arden + 1 + 1 + + + 15000 + arden@gmail.com + 2653241536 + + + + + Dr.Armstrong + 1 + 0 + + + 26000 + armstrong@gmail.com + 2653651436 + + + + + Dr.Saunder + 1 + 0 + + + 78000 + saunder@yahoo.com + 2653365436 + + + + + Dr.Asbjorn + 1 + 0 + + + 75000 + asbjorn@yahoo.com + 2657415436 + + + + + Dr.Salomon + 1 + 0 + + + 85000 + salomon@yahoo.com + 4877415436 + + + + Dr.Saunderson + 1 + 0 + + + 65000 + saunderson@gmail.com + 6987415436 + + + + + \ No newline at end of file diff --git a/med_marketing/models/__init__.py b/med_marketing/models/__init__.py new file mode 100644 index 000000000..0aa7b7765 --- /dev/null +++ b/med_marketing/models/__init__.py @@ -0,0 +1 @@ +import doctor,med_marketing,rep,doctor_exp \ No newline at end of file diff --git a/med_marketing/models/doctor.py b/med_marketing/models/doctor.py new file mode 100644 index 000000000..315c7d5a5 --- /dev/null +++ b/med_marketing/models/doctor.py @@ -0,0 +1,33 @@ +from openerp import models, fields, api +import datetime +from dateutil import parser + + +class Doctors(models.Model): + _inherit = 'res.partner' + + doctor = fields.Boolean('Doctor') + specialist_in = fields.Many2one('pha_marketing.departments', 'Specialist in') + related_rep = fields.Many2one('hr.employee', string='Related Rep.', domain="[('rep','=',True)]") + target = fields.Integer('Target') + + @api.one + def _get_total_sale(self): + total = 0.0 + created_domain = [('type', 'in', ['out_invoice', 'out_refund']), ('state', 'not in', ['draft', 'cancel']), ] + for EACH_REPORT in self.env['account.invoice.report'].search(created_domain): + if self.zip == EACH_REPORT.partner_id.zip: + if self.specialist_in.id in EACH_REPORT.product_id.uses_in.ids: + if parser.parse(EACH_REPORT.date).month == datetime.date.today().month: + total_doctor = 0 + for EACH_Doctor in self.search([('doctor', '=', True)]): + if EACH_Doctor.zip == self.zip and EACH_Doctor.specialist_in.id in EACH_REPORT.product_id.uses_in.ids: + + total_doctor += 1 + if total_doctor != 0: + total += (EACH_REPORT.price_total/total_doctor) + + + self.total_sale = total + total_sale = fields.Integer('Total sale', compute='_get_total_sale', help='Will calculate the doctors by considering zip code of both Doctors and Retailers and compare the department of doctor with use of sold medicines.') + diff --git a/med_marketing/models/doctor_exp.py b/med_marketing/models/doctor_exp.py new file mode 100644 index 000000000..0e7cb3166 --- /dev/null +++ b/med_marketing/models/doctor_exp.py @@ -0,0 +1,72 @@ +from openerp import models, fields,api +import datetime + + +class DoctorBenefit(models.Model): + _name = 'med_marketing.doctor.line' + + name = fields.Char('Description') + doctor_id = fields.Many2one('res.partner') + account_id = fields.Many2one('account.account', 'Account') + amt = fields.Float('Cost') + exp_account = fields.Many2one('account.account', 'Expenses Account') + state = fields.Selection([('unposted', 'Draft'), ('posted', 'Posted'), ], default="unposted") + journal_id = fields.Many2one('account.journal', 'Journal') + + @api.one + def post_confirm(self): + self.state = 'posted' + ref = 'DRX' + str(datetime.datetime.now().year) + str(self.id).zfill(4) + num = 'DRX' + '/' + str(datetime.datetime.now().year) + '/' + str(self.id).zfill(4) + if self.name == False: + description = '' + else: + description = self.name + j_lines_list = [] + j_lines_1 = {'name': description, + 'partner_id': None, + 'account_id': self.account_id.id, + 'debit': 0.0, + 'credit': self.amt, + } + + j_lines_list.append((0, 0, j_lines_1)) + j_lines_2 = {'name': description, + 'partner_id': None, + 'account_id': self.exp_account.id, + 'debit': self.amt, + 'credit': 0.0, + } + + j_lines_list.append((0, 0, j_lines_2)) + + j_values = {'name': num, + 'journal_id': self.journal_id.id, + 'ref': ref, + 'state': 'posted' + } + + j_values.update({'line_id': j_lines_list}) + j_obj = self.pool.get('account.move') + j_obj.create(self._cr, self._uid, j_values) + + +class Doctors(models.Model): + _inherit = 'res.partner' + + @api.one + def get_current_id(self): + return self.id + + def doctor_exp_btn(self, cr, uid, ids, context=None): + created_domain = '[("doctor_id","in",'+str(self.get_current_id(cr, uid, ids, context))+')]' + return { + 'name':'Doctor Expenses', + 'type': 'ir.actions.act_window', + 'res_model': 'med_marketing.doctor.line', + 'view_type': 'form', + 'view_mode': 'tree,form', + 'target': 'current', + 'domain': created_domain, + } + diff --git a/med_marketing/models/med_marketing.py b/med_marketing/models/med_marketing.py new file mode 100644 index 000000000..bede7ae8a --- /dev/null +++ b/med_marketing/models/med_marketing.py @@ -0,0 +1,14 @@ +from openerp import models, fields,api + + +class MedicalDepartments(models.Model): + _name = 'pha_marketing.departments' + + name = fields.Char('Name of Department') + + +class MedicinesInfo(models.Model): + _inherit = "product.template" + + uses_in = fields.Many2many('pha_marketing.departments', 'name') + diff --git a/med_marketing/models/rep.py b/med_marketing/models/rep.py new file mode 100644 index 000000000..69a217105 --- /dev/null +++ b/med_marketing/models/rep.py @@ -0,0 +1,18 @@ +from openerp import models, fields,api + + +class RepEmployee(models.Model): + _inherit = 'hr.employee' + + rep = fields.Boolean('Medical Representative') + commission = fields.Float('Commission') + target = fields.Integer('Target') + + @api.one + def _get_total_sale(self): + total = 0.0 + for EACH_Doctor in self.env['res.partner'].search([('doctor', '=', True)]): + if EACH_Doctor.related_rep.id == self.id: + total += EACH_Doctor.total_sale + self.total_sale = total + total_sale = fields.Integer('Total sale', compute='_get_total_sale') diff --git a/med_marketing/static/description/icon.png b/med_marketing/static/description/icon.png new file mode 100644 index 000000000..69c614c7c Binary files /dev/null and b/med_marketing/static/description/icon.png differ diff --git a/med_marketing/static/img/dr1.jpg b/med_marketing/static/img/dr1.jpg new file mode 100644 index 000000000..5c82e606a Binary files /dev/null and b/med_marketing/static/img/dr1.jpg differ diff --git a/med_marketing/static/img/dr2.jpg b/med_marketing/static/img/dr2.jpg new file mode 100644 index 000000000..cf6f586c4 Binary files /dev/null and b/med_marketing/static/img/dr2.jpg differ diff --git a/med_marketing/static/img/dr3.jpg b/med_marketing/static/img/dr3.jpg new file mode 100644 index 000000000..55dd170b3 Binary files /dev/null and b/med_marketing/static/img/dr3.jpg differ diff --git a/med_marketing/static/img/dr4.jpg b/med_marketing/static/img/dr4.jpg new file mode 100644 index 000000000..44e096512 Binary files /dev/null and b/med_marketing/static/img/dr4.jpg differ diff --git a/med_marketing/static/img/dr6.jpg b/med_marketing/static/img/dr6.jpg new file mode 100644 index 000000000..3b3c1c281 Binary files /dev/null and b/med_marketing/static/img/dr6.jpg differ diff --git a/med_marketing/static/img/dr7.jpg b/med_marketing/static/img/dr7.jpg new file mode 100644 index 000000000..8ca244aa0 Binary files /dev/null and b/med_marketing/static/img/dr7.jpg differ diff --git a/med_marketing/views/doctor_exp_view.xml b/med_marketing/views/doctor_exp_view.xml new file mode 100644 index 000000000..889024ceb --- /dev/null +++ b/med_marketing/views/doctor_exp_view.xml @@ -0,0 +1,81 @@ + + + + + + + med_marketing.doctor.line.form + med_marketing.doctor.line + +
+
+
+ + + + + + + + + + + + + + + + +
+
+
+ + + med_marketing.doctor.line.tree + med_marketing.doctor.line + + + + + + + + + + + + + + Doctor Benefits + med_marketing.doctor.line + form + tree,form + [] + {} + +

+ Click to add a new doctor benefits. +

+
+
+ + + + +
+
\ No newline at end of file diff --git a/med_marketing/views/doctor_view.xml b/med_marketing/views/doctor_view.xml new file mode 100644 index 000000000..6041ffaca --- /dev/null +++ b/med_marketing/views/doctor_view.xml @@ -0,0 +1,81 @@ + + + + + + + res.partner.doctor + res.partner + + + + + + + + + + + res.partner.doctor + res.partner + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/med_marketing/views/rep_view.xml b/med_marketing/views/rep_view.xml new file mode 100644 index 000000000..ccf98ca47 --- /dev/null +++ b/med_marketing/views/rep_view.xml @@ -0,0 +1,65 @@ + + + + + + Employees + hr.employee + + + + + + + + + + + + hr.employee.form.inherit + hr.employee + + + +

+

+
+
+ + + + + + + +
+
+ + + Medical Representatives + hr.employee + form + kanban,tree,form + [] + {"search_default_filter_rep":1, "default_rep":1} + + + +

+ Click to add a new employee. +

+ With just a quick glance on the Odoo employee screen, you + can easily find all the information you need for each person; + contact data, job position, availability, etc. +

+
+
+ + + +
+
diff --git a/medical_feature/__init__.py b/medical_feature/__init__.py new file mode 100644 index 000000000..29d807e14 --- /dev/null +++ b/medical_feature/__init__.py @@ -0,0 +1 @@ +import pharmacy_mgt,route_manage,expiry_manage diff --git a/medical_feature/__openerp__.py b/medical_feature/__openerp__.py new file mode 100644 index 000000000..7eb5cb9b5 --- /dev/null +++ b/medical_feature/__openerp__.py @@ -0,0 +1,28 @@ +{ + 'name': "Pharmacy Management", + 'summary': """ Some Basics For Field of Medicine """, + 'description': """ """, + 'author': 'Cybrosys Techno Solutions', + 'company': 'Cybrosys Techno Solutions', + 'license': 'AGPL-3', + 'category': 'Medicine', + 'version': '0.1', + 'depends': ["base", + "sale", + "purchase", + "stock", + "mrp", + "mrp_operations", + "product_expiry", + "account_accountant"], + 'data': ['views/pharmacy_mgt_view.xml', + 'views/medicines_view.xml', + 'route_manage/route_manage_view.xml', + 'route_manage/report.xml', 'new_names.xml', + 'expiry_manage/expiry_manage_view.xml', + ], + 'demo': ['demo/demo.xml'], + 'installable': True, + 'auto_install': False, + 'application': True, +} diff --git a/medical_feature/demo/demo.xml b/medical_feature/demo/demo.xml new file mode 100644 index 000000000..a02d7e7bd --- /dev/null +++ b/medical_feature/demo/demo.xml @@ -0,0 +1,335 @@ + + + + + + + Sm street + + + Manorama Junction + + + East Nadakavu Junction + + + + Sarovaram + + + + Mavoor Road + + + + Malakunta + + + + Gruhkalpa + + + + Band colony + + + + Sultan Bazaar + + + + Esamiya Bazaar + + + + Royapettah + + + + Teynampet + + + + T Nagar + + + + Kodambakkam + + + + Arumbakkam + + + + + + Lal Bazaar + + + + Bow Bazaar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Kolkata + KLT + + + + + + Mumbai + MBI + + + + + New Delhi + DLH + + + + + + Chennai + CHN + + + + + + Hyderabad + HBD + + + + + + Calicut + CLT + + + + + + + 11 + Eye + + + 12 + Ear, Nose and Oropharynx + + + 13 + Skin + + + 14 + Infections and Infestations + + + 15 + Allergic disorders + + + 16 + Gastrointestinal tract + + + 17 + Nutrition + + + + + + Acetylcysteine + 10.0 + 10.0 + + + + + + Carbomer Liquid Eye Gels + 10.0 + 10.0 + + + + + + + Azithromycin + 10.0 + 10.0 + + + + + + Amoxicillin + 10.0 + 10.0 + + + + + + + Clarithromycin + 10.0 + 10.0 + + + + + + Celiprolol + 10.0 + 10.0 + + + + + + Cetirizine + 10.0 + 10.0 + + + + + + Dimethindene + 10.0 + 10.0 + + + + + + Aciphex + 10.0 + 10.0 + + + + + + Gastrogafin + 10.0 + 10.0 + + + + + + Gleevec + 10.0 + 10.0 + + + + + + + + AE00G5K220001 + + + + + + AE00G5K220002 + + + + + + AE00G5K220003 + + + + + + + \ No newline at end of file diff --git a/medical_feature/expiry_manage/__init__.py b/medical_feature/expiry_manage/__init__.py new file mode 100644 index 000000000..189ff2d34 --- /dev/null +++ b/medical_feature/expiry_manage/__init__.py @@ -0,0 +1 @@ +import expiry_manage \ No newline at end of file diff --git a/medical_feature/expiry_manage/expiry_manage.py b/medical_feature/expiry_manage/expiry_manage.py new file mode 100644 index 000000000..1f48d51ef --- /dev/null +++ b/medical_feature/expiry_manage/expiry_manage.py @@ -0,0 +1,52 @@ +from openerp import models, fields, _ +from openerp import api +import datetime +from dateutil import parser + + +class StockTransferDetailsItems(models.TransientModel): + _inherit = 'stock.transfer_details_items' + + lot_id = fields.Many2one('stock.production.lot', 'Lot/Serial Number') + + @api.onchange('lot_id') + def when_items_changes_(self): + # ALERT DATE [alert_date] + if self.lot_id.alert_date is not False and parser.parse(self.lot_id.alert_date) < datetime.datetime.now(): + return {'warning': {'title': _('Warning'), 'message': _('The lot \''+self.lot_id.name+'\' of '+' medicine \''+self.lot_id.product_id.name+'\'\n must not be consumed and should be removed from the stock.')}} + # REMOVAL DATE [removal_date] + if self.lot_id.removal_date is not False and parser.parse(self.lot_id.removal_date) < datetime.datetime.now(): + return {'warning': {'title': _('Warning'), 'message': _('The lot \''+self.lot_id.name+'\' of '+' medicine \''+self.lot_id.product_id.name+'\'\n must not be consumed and should be removed from the stock.')}} + # END OF LIFE DATE [life_date] + if self.lot_id.life_date is not False and parser.parse(self.lot_id.life_date) < datetime.datetime.now(): + return {'warning': {'title': _('Warning'), 'message': _('The lot \''+self.lot_id.name+'\' of '+' medicine \''+self.lot_id.product_id.name+'\'\n is dangerous and must not be consumed.')}} + # BEST BEFORE DATE [use_date] + if self.lot_id.use_date is not False and parser.parse(self.lot_id.use_date) < datetime.datetime.now(): + return {'warning': {'title': _('Warning'), 'message': _('The lot \''+self.lot_id.name+'\' of '+' medicine \''+self.lot_id.product_id.name+'\'\n is not good for use now.')}} + + +class AddColorSerialNo(models.Model): + _inherit = 'stock.production.lot' + + def _get_default_vals(self): + for each_lots in self: + if each_lots.alert_date is not False and parser.parse(each_lots.alert_date) < datetime.datetime.now(): + each_lots.state = 'red' + elif each_lots.removal_date is not False and parser.parse(each_lots.removal_date) < datetime.datetime.now(): + each_lots.state = 'red' + elif each_lots.life_date is not False and parser.parse(each_lots.life_date) < datetime.datetime.now(): + each_lots.state = 'red' + elif each_lots.use_date is not False and parser.parse(each_lots.use_date) < datetime.datetime.now(): + each_lots.state = 'lite_red' + else: + each_lots.state = 'normal' + + + state = fields.Selection([('normal', 'Normal'), + ('lite_red', 'Not Good'), + ('red', 'To Be Removed')], + string='Status', + compute=lambda self: self._get_default_vals() + # default=lambda self: self._get_default_vals() + ) + diff --git a/medical_feature/expiry_manage/expiry_manage_view.xml b/medical_feature/expiry_manage/expiry_manage_view.xml new file mode 100644 index 000000000..a28028f84 --- /dev/null +++ b/medical_feature/expiry_manage/expiry_manage_view.xml @@ -0,0 +1,56 @@ + + + + + + stock.production.lot.tree + stock.production.lot + + + + + + + + + + + + + Production Lots Filter + stock.production.lot + + + + + + + + + + + + + Serial Numbers + ir.actions.act_window + stock.production.lot + form + + + {} + +

+ Click to add a serial number. +

+ This is the list of all the production lots you recorded. When + you select a lot, you can get the traceability of the products contained in lot. +

+
+
+ + +
+
\ No newline at end of file diff --git a/medical_feature/new_names.xml b/medical_feature/new_names.xml new file mode 100644 index 000000000..c50e07d87 --- /dev/null +++ b/medical_feature/new_names.xml @@ -0,0 +1,170 @@ + + + + + + + + Retailers + + + + Retailers + + + + + + + Draft Orders + + + Draft Orders + + + + + + Confirmed Orders + + + Confirmed Orders + + + + + + Medicines + + + + Medicines + + + Medicines + + + + + + Medicines Variants + + + Medicines Variants + + + + + + + + + + + + + + + + + Draft Orders + + + Draft Orders + + + + + + Confirmed Orders + + + Confirmed Orders + + + + + + Vendors + + + Vendors + + + + + + Medicines + + + + Medicines + + + Medicines + + + + + + + + + + + + + + + Talks + + + + + + + Stocks + + + + Medicines + + + + + + Medicines + + + + + + + + Medicines + + + + Medicines + + + Medicines + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/medical_feature/pharmacy_mgt.py b/medical_feature/pharmacy_mgt.py new file mode 100644 index 000000000..93ebd2b7d --- /dev/null +++ b/medical_feature/pharmacy_mgt.py @@ -0,0 +1,32 @@ +from openerp import models, fields,api + + +class Medicines(models.Model): + _inherit = 'product.template' + + medicine_category = fields.Selection([('allopathy', 'Allopathy'), + ('ayurvedic', 'Ayurvedic'), + ('homeo', 'Homeo'), + ('generic', 'Generic'), + ('none', 'None Medicine'), + ('veterinary', 'Veterinary'), ], 'Medicine Type', default='generic') + medicine_type = fields.Many2one('product.medicine.types', 'Medicine Category') + product_of = fields.Many2one('product.medicine.responsible', 'Product Of') + + +class MedicineTypes(models.Model): + _name = 'product.medicine.types' + _rec_name = 'medicine_type' + + medicine_type = fields.Char(string="Medicine Category") + + +class MedicineResponsible(models.Model): + _name = 'product.medicine.responsible' + _rec_name = 'name_responsible' + + name_responsible = fields.Char(string="Product Of ") + related_vendor = fields.Many2one('res.partner', string='Related Vendor', domain="[('supplier', '=','1')]") + place = fields.Char(string="Place") + + diff --git a/medical_feature/route_manage/__init__.py b/medical_feature/route_manage/__init__.py new file mode 100644 index 000000000..36649b9a8 --- /dev/null +++ b/medical_feature/route_manage/__init__.py @@ -0,0 +1 @@ +import route_mgt diff --git a/medical_feature/route_manage/report.xml b/medical_feature/route_manage/report.xml new file mode 100644 index 000000000..78345b66f --- /dev/null +++ b/medical_feature/route_manage/report.xml @@ -0,0 +1,30 @@ + + + + + + sale.order.route + sale.order + + + + + + + + + + + Sales Analysis By Routes + sale.order + + graph + + + + + + \ No newline at end of file diff --git a/medical_feature/route_manage/route_manage_view.xml b/medical_feature/route_manage/route_manage_view.xml new file mode 100644 index 000000000..7d500765c --- /dev/null +++ b/medical_feature/route_manage/route_manage_view.xml @@ -0,0 +1,237 @@ + + + + + + pharmacy_management.route.kanban + pharmacy_management.route + + + + + + + + + + + +
+

+ +

+

+ +

+
+
+ +
+
+ + + + + +
+ + +
+
+
+
+
+
+
+
+ + + pharmacy_management.route.select + pharmacy_management.route + + + + + + + + + Routes + pharmacy_management.route + form + kanban,tree,form + {"search_default_my_route":1} + + + + + pharmacy_management.route.tree + pharmacy_management.route + + + + + + + + + + pharmacy_management.route.form + pharmacy_management.route + +
+ + + + + + +
+ + + + + + + + +
+ + + + + + + + + + + + +
+ + + +
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + res.partner.route + res.partner + + + + + + + + + sale.order.customer_route + sale.order + + + + + + + + + + + Retailers + ir.actions.act_window + res.partner + form + kanban,tree,form + {"search_default_customer":1 ,"search_default_my_retailers":1} + + +

+ Click to add a contact in your address book. +

+ Odoo helps you easily track all activities related to + a customer; discussions, history of business opportunities, + documents, etc. +

+
+
+ + + res.partner.serach + res.partner + + + + + + + + + + + sale.order.form.inherit269 + sale.order + + + + [('customer','=',True), ('id_of_route_distributor','=',uid)] + {'search_default_customer':1, 'show_address': 1, 'search_default_my_retailers': 1 } + + + + + + pharmacy_management.route.location.tree + pharmacy_management.route.location + + + + + + + + + pharmacy_management.route.location.form + pharmacy_management.route.location + +
+ + + + + +
+
+
+ + +
+
\ No newline at end of file diff --git a/medical_feature/route_manage/route_mgt.py b/medical_feature/route_manage/route_mgt.py new file mode 100644 index 000000000..aa5d128a6 --- /dev/null +++ b/medical_feature/route_manage/route_mgt.py @@ -0,0 +1,89 @@ +from openerp import models, fields,_ +from openerp import api + + +# ROUTE +class DistributionRoutes(models.Model): + _name = 'pharmacy_management.route' + + name = fields.Char('Route Name') + route_code = fields.Char() + distributor = fields.Many2one('res.users', 'Distributor') + customer_list = fields.One2many('res.partner', 'customer_route') + + @api.one + def _get_customer_count(self): + self.count_customers = len(self.customer_list) + count_customers = fields.Integer(compute='_get_customer_count') + # location_list = fields.One2many('pharmacy_management.route.location', 'route_id') + location_list1 = fields.Many2many('pharmacy_management.route.location', 'route_id', string='Locations') + + def locations_btn(self, cr, uid, ids, context=None): + return { + 'view_type': 'form', + 'view_mode': 'tree,form', + 'res_model': 'pharmacy_management.route.location', + 'view_id': False, + 'type': 'ir.actions.act_window', + 'name' : _('All Route Locations'), + } + + +# RETAILERS +class RouteInCustomer(models.Model): + _inherit = 'res.partner' + + @api.onchange('customer_route') + def set_id_of_route_distributor(self): + print self.customer_route.distributor.id + self.id_of_route_distributor = self.customer_route.distributor.id + + customer_route = fields.Many2one('pharmacy_management.route', 'Route') + id_of_route_distributor = fields.Integer() + + +# SALE ORDER +class RouteInSaleOrder(models.Model): + _inherit = 'sale.order' + + route_of_customer = fields.Many2one('pharmacy_management.route', 'Route of Customer') + + def onchange_partner_id(self, cr, uid, ids, part, context=None): + if not part: + return {'value': {'partner_invoice_id': False, 'partner_shipping_id': False, 'payment_term': False, 'fiscal_position': False}} + + part = self.pool.get('res.partner').browse(cr, uid, part, context=context) + addr = self.pool.get('res.partner').address_get(cr, uid, [part.id], ['delivery', 'invoice', 'contact']) + pricelist = part.property_product_pricelist and part.property_product_pricelist.id or False + invoice_part = self.pool.get('res.partner').browse(cr, uid, addr['invoice'], context=context) + payment_term = invoice_part.property_payment_term and invoice_part.property_payment_term.id or False + dedicated_salesman = part.user_id and part.user_id.id or uid + val = { + 'partner_invoice_id': addr['invoice'], + 'partner_shipping_id': addr['delivery'], + 'payment_term': payment_term, + 'user_id': dedicated_salesman, + } + delivery_onchange = self.onchange_delivery_id(cr, uid, ids, False, part.id, addr['delivery'], False, context=context) + val.update(delivery_onchange['value']) + if pricelist: + val['pricelist_id'] = pricelist + if not self._get_default_section_id(cr, uid, context=context) and part.section_id: + val['section_id'] = part.section_id.id + sale_note = self.get_salenote(cr, uid, ids, part.id, context=context) + if sale_note: val.update({'note': sale_note}) + # ================================================= + val['route_of_customer'] = part.customer_route.id + # val['user_id'] = part.customer_route.distributor.id + return {'value': val} + + def _get_route_of_customer_to_show(self): + self.route_of_customer_to_show = self.partner_id.customer_route.id + route_of_customer_to_show = fields.Many2one('pharmacy_management.route', 'Route of Customer' , compute='_get_route_of_customer_to_show') + + +class Locations(models.Model): + _name = 'pharmacy_management.route.location' + + name = fields.Char('Location Name') + route_id = fields.Many2one('pharmacy_management.route') \ No newline at end of file diff --git a/medical_feature/static/description/icon.png b/medical_feature/static/description/icon.png new file mode 100644 index 000000000..69c614c7c Binary files /dev/null and b/medical_feature/static/description/icon.png differ diff --git a/medical_feature/static/img/Acetylcysteine.jpg b/medical_feature/static/img/Acetylcysteine.jpg new file mode 100644 index 000000000..89524fe46 Binary files /dev/null and b/medical_feature/static/img/Acetylcysteine.jpg differ diff --git a/medical_feature/static/img/Azithromycin.jpg b/medical_feature/static/img/Azithromycin.jpg new file mode 100644 index 000000000..1cc9408b8 Binary files /dev/null and b/medical_feature/static/img/Azithromycin.jpg differ diff --git a/medical_feature/static/img/Carbomer_liquid_eye_gels.jpg b/medical_feature/static/img/Carbomer_liquid_eye_gels.jpg new file mode 100644 index 000000000..c26cc6ce6 Binary files /dev/null and b/medical_feature/static/img/Carbomer_liquid_eye_gels.jpg differ diff --git a/medical_feature/static/img/allr_Celiprolol.jpg b/medical_feature/static/img/allr_Celiprolol.jpg new file mode 100644 index 000000000..dc3aae0f4 Binary files /dev/null and b/medical_feature/static/img/allr_Celiprolol.jpg differ diff --git a/medical_feature/static/img/allr_Cetirizine.jpg b/medical_feature/static/img/allr_Cetirizine.jpg new file mode 100644 index 000000000..81492cdb1 Binary files /dev/null and b/medical_feature/static/img/allr_Cetirizine.jpg differ diff --git a/medical_feature/static/img/allr_Dimethindene.jpg b/medical_feature/static/img/allr_Dimethindene.jpg new file mode 100644 index 000000000..0fb76a69f Binary files /dev/null and b/medical_feature/static/img/allr_Dimethindene.jpg differ diff --git a/medical_feature/static/img/er_amoxicillin-1.jpg b/medical_feature/static/img/er_amoxicillin-1.jpg new file mode 100644 index 000000000..db134bf53 Binary files /dev/null and b/medical_feature/static/img/er_amoxicillin-1.jpg differ diff --git a/medical_feature/static/img/gs_Aciphex.jpg b/medical_feature/static/img/gs_Aciphex.jpg new file mode 100644 index 000000000..1ea63d1c1 Binary files /dev/null and b/medical_feature/static/img/gs_Aciphex.jpg differ diff --git a/medical_feature/static/img/gs_Gleevec.jpg b/medical_feature/static/img/gs_Gleevec.jpg new file mode 100644 index 000000000..70fcec751 Binary files /dev/null and b/medical_feature/static/img/gs_Gleevec.jpg differ diff --git a/medical_feature/static/img/gs_gastrogafin.jpg b/medical_feature/static/img/gs_gastrogafin.jpg new file mode 100644 index 000000000..52bcd2a3c Binary files /dev/null and b/medical_feature/static/img/gs_gastrogafin.jpg differ diff --git a/medical_feature/static/img/re_clarithromycin.jpg b/medical_feature/static/img/re_clarithromycin.jpg new file mode 100644 index 000000000..51fe0ab2f Binary files /dev/null and b/medical_feature/static/img/re_clarithromycin.jpg differ diff --git a/medical_feature/views/medicines_view.xml b/medical_feature/views/medicines_view.xml new file mode 100644 index 000000000..435681301 --- /dev/null +++ b/medical_feature/views/medicines_view.xml @@ -0,0 +1,28 @@ + + + + + + + Medicines + ir.actions.act_window + product.template + kanban,tree,form + form + + {"search_default_filter_to_sell":1, "search_default_medicine_type":1,} + + + + + + + + + \ No newline at end of file diff --git a/medical_feature/views/pharmacy_mgt_view.xml b/medical_feature/views/pharmacy_mgt_view.xml new file mode 100644 index 000000000..0f4559503 --- /dev/null +++ b/medical_feature/views/pharmacy_mgt_view.xml @@ -0,0 +1,174 @@ + + + + + + Medical Features Manager + + + + + + + + + + + + product.medicine.types.form + product.medicine.types + +
+ + + + + +
+
+
+ + + product.medicine.types.tree + product.medicine.types + + + + + + + + + Medicine Categories + product.medicine.types + form + tree,form + + + + + + + product.template.form.inherited + product.template + + + + + + + + + + + + + + product.template.kanban.inherited + product.template + + + + + + + + + + + + + product.template.form.inherited.new + product.template + + + + + + + + + + + + Medicines + ir.actions.act_window + product.template + kanban,tree,form + form + + {"search_default_filter_to_sell":1, "search_default_medicine_type":1,} + + + + + + + product.medicine.responsible.form + product.medicine.responsible + +
+ + + + + + + +
+
+
+ + + product.medicine.responsible.tree + product.medicine.responsible + + + + + + + + + + + Medicine Responsible + product.medicine.responsible + form + tree,form + + + + + + Manage Lots / Serial Numbers + + + + + + + +
+
\ No newline at end of file diff --git a/pos_product_category_filter/__init__.py b/pos_product_category_filter/__init__.py new file mode 100644 index 000000000..2c4eac3f8 --- /dev/null +++ b/pos_product_category_filter/__init__.py @@ -0,0 +1 @@ +import models \ No newline at end of file diff --git a/pos_product_category_filter/__openerp__.py b/pos_product_category_filter/__openerp__.py new file mode 100644 index 000000000..deb0fb616 --- /dev/null +++ b/pos_product_category_filter/__openerp__.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + + +{ + 'name': 'POS Product Category Filter', + 'version': '1.0', + 'category': 'Point of Sale', + 'sequence': 6, + 'summary': 'Show only specified categories of product in point of sale ', + 'author': 'Cybrosys Techno Solutions', + 'company': 'Cybrosys Techno Solutions', + 'website': 'http://www.cybrosys.com', + 'description': """ + +======================= + + + +""", + 'depends': ['point_of_sale'], + 'data': ['views/custom_view.xml', + 'views/template.xml'], + 'qweb': [], + 'installable': True, + 'auto_install': False, +} diff --git a/pos_product_category_filter/__openerp__.py~ b/pos_product_category_filter/__openerp__.py~ new file mode 100644 index 000000000..a425ba2e2 --- /dev/null +++ b/pos_product_category_filter/__openerp__.py~ @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + + +{ + 'name': 'POS Product Category Filter', + 'version': '1.0', + 'category': 'Point of Sale', + 'sequence': 6, + 'summary': 'Show only specified categories of product in point of sale ', + 'author': 'Cybrosys Techno Solutions', + 'company': 'Cybrosys Techno Solutions', + 'website': 'http://www.cybrosys.com', + 'description': """ + +======================= + +Customization of produc availablity in PoS. + +""", + 'depends': ['point_of_sale'], + 'data': ['views/custom_view.xml', + 'views/template.xml'], + 'qweb': [], + 'installable': True, + 'auto_install': False, +} diff --git a/pos_product_category_filter/models/__init__.py b/pos_product_category_filter/models/__init__.py new file mode 100644 index 000000000..5bf9da604 --- /dev/null +++ b/pos_product_category_filter/models/__init__.py @@ -0,0 +1 @@ +import custom \ No newline at end of file diff --git a/pos_product_category_filter/models/custom.py b/pos_product_category_filter/models/custom.py new file mode 100644 index 000000000..5579cbb5a --- /dev/null +++ b/pos_product_category_filter/models/custom.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from openerp import models, fields, api + +class PosProductCateg(models.Model): + + _inherit = 'pos.config' + + available_categ = fields.Many2many('pos.category', string='Available Product Categories') + + +# +class PosCateg(models.Model): +# + _name = 'pos.custom' + diff --git a/pos_product_category_filter/static/description/index.html b/pos_product_category_filter/static/description/index.html new file mode 100644 index 000000000..b8d40b857 --- /dev/null +++ b/pos_product_category_filter/static/description/index.html @@ -0,0 +1,32 @@ +
+
+

Point of Sale : Setting Product Categories for POS

+ +
+

+By installing this module, we can set available product categories for each POS in our system. +Only those categories set in the POS will be available in that particular POS. +

+
+
+
+ +
+
+

You can set it in POS form as:

+
+

+ Point of sale > Configuration > Point of Sale > Available Product Categories. +

+
+
+
+ +
+
+
+
+ + +
+
diff --git a/pos_product_category_filter/static/description/index.html~ b/pos_product_category_filter/static/description/index.html~ new file mode 100644 index 000000000..d96f49fce --- /dev/null +++ b/pos_product_category_filter/static/description/index.html~ @@ -0,0 +1,48 @@ +
+
+

Point of Sale : Setting Product Categories for POS

+ +
+

+By installing this module, we can set available product categories for each POS in our system. +Only those categories set in the POS will be available in that particular POS. +

+
+
+
+ +
+
+

You can set it in POS form as:

+
+

+ Point of sale > Configuration > Point of Sale > Available Product Categories. +

+
+
+
+ +
+
+
+
+ +
+
+

Setting Product category

+
+

+1) If a root category is given all the subcategories and products under that category will be available. +

+

+2) If an intermediate category(ie. one with parent and child categories) is given then parent category and all child categories will be available. +

+

+3) If a child category(ie. one without any child categories), the category can be reached by traversing its ancestor categories. +

+
+
+
+ +
+
diff --git a/pos_product_category_filter/static/description/pos.png b/pos_product_category_filter/static/description/pos.png new file mode 100644 index 000000000..3df67a4b8 Binary files /dev/null and b/pos_product_category_filter/static/description/pos.png differ diff --git a/pos_product_category_filter/static/src/js/category.js b/pos_product_category_filter/static/src/js/category.js new file mode 100644 index 000000000..0da27919c --- /dev/null +++ b/pos_product_category_filter/static/src/js/category.js @@ -0,0 +1,214 @@ +openerp.pos_product_category_filter = function(instance){ + var module = instance.point_of_sale; + var models = module.PosModel.prototype; + var round_pr = instance.web.round_precision + + var model1 = { + model: 'pos.category', + fields: ['id','name','parent_id','child_id','image'], + domain: null, + loaded: function(self, categories){ + for(var i = 0, len = categories.length; i < len; i++){ + categories[i].available = false; + for(var j = 0, jlen = self.config.available_categ.length; j < jlen; j++){ + if(categories[i].id == self.config.available_categ[j]){ + categories[i].available = true; + } + } + } + self.db.add_categories(categories); + }, + } + + for(var i=0; i< models.models.length; i++){ + var model=models.models[i]; + if(model.model === 'pos.category'){ + models.models[i] = model1; + } + } + + + module.PosDB.include({ + add_categories: function(categories){ + + var self = this; + + if(!this.category_by_id[this.root_category_id]){ + this.category_by_id[this.root_category_id] = { + id : this.root_category_id, + name : 'Root', + }; + } + for(var i=0, len = categories.length; i < len; i++){ + if(categories[i].available === true){ + categories[i].parent_id[0] = 0; + } + } + for(var i=0, len = categories.length; i < len; i++){ + this.category_by_id[categories[i].id] = categories[i]; + } + for(var i=0, len = categories.length; i < len; i++){ + var cat = categories[i]; + var parent_id = cat.parent_id[0] || this.root_category_id; + this.category_parent[cat.id] = cat.parent_id[0]; + if(!this.category_childs[parent_id]){ + this.category_childs[parent_id] = []; + } + this.category_childs[parent_id].push(cat.id); + } + + function make_ancestors(cat_id, ancestors){ + self.category_ancestors[cat_id] = ancestors; + + ancestors = ancestors.slice(0); + ancestors.push(cat_id); + + var childs = self.category_childs[cat_id] || []; + for(var i=0, len = childs.length; i < len; i++){ + make_ancestors(childs[i], ancestors); + } + } + make_ancestors(this.root_category_id, []); + }, + add_products: function(products){ + var stored_categories = this.product_by_category_id; + for(var i = 0, len = products.length; i < len; i++){ + var ancestor_ids = this.get_category_ancestors_ids(products[i].pos_categ_id[0]); + products[i].pos_categ_ancestors = ancestor_ids; + } + for(var i = 0, len = products.length; i < len; i++){ + products[i].available = false; + var category = this.get_category_by_id(products[i].pos_categ_id[0]); + if(category && category.available == true){ + products[i].available = true; + }else{ + if(products[i].pos_categ_ancestors){ + for(var j = 0, jlen = products[i].pos_categ_ancestors.length; j < jlen; j++){ + var temp_categ = this.get_category_by_id(products[i].pos_categ_ancestors[j]) + if(temp_categ != 0 && temp_categ.available == true){ + products[i].available = true; + } + } + } + } + } + + if(!products instanceof Array){ + products = [products]; + } + for(var i = 0, len = products.length; i < len; i++){ + var product = products[i]; + var search_string = this._product_search_string(product); + var categ_id = product.pos_categ_id ? product.pos_categ_id[0] : this.root_category_id; + product.product_tmpl_id = product.product_tmpl_id[0]; + if(!stored_categories[categ_id]){ + stored_categories[categ_id] = []; + } + stored_categories[categ_id].push(product.id); + + if(this.category_search_string[categ_id] === undefined){ + this.category_search_string[categ_id] = ''; + } + this.category_search_string[categ_id] += search_string; + + var ancestors = this.get_category_ancestors_ids(categ_id) || []; + + for(var j = 0, jlen = ancestors.length; j < jlen; j++){ + var ancestor = ancestors[j]; + if(! stored_categories[ancestor]){ + stored_categories[ancestor] = []; + } + stored_categories[ancestor].push(product.id); + + if( this.category_search_string[ancestor] === undefined){ + this.category_search_string[ancestor] = ''; + } + this.category_search_string[ancestor] += search_string; + } + this.product_by_id[product.id] = product; + if(product.ean13){ + this.product_by_ean13[product.ean13] = product; + } + if(product.default_code){ + this.product_by_reference[product.default_code] = product; + } + } + }, + }); + + module.ProductCategoriesWidget.include({ + renderElement: function(){ + var db = this.pos.db; + var self = this; + + var el_str = openerp.qweb.render(this.template, {widget: this}); + var el_node = document.createElement('div'); + el_node.innerHTML = el_str; + el_node = el_node.childNodes[1]; + + if(this.el && this.el.parentNode){ + this.el.parentNode.replaceChild(el_node,this.el); + } + + this.el = el_node; + + var hasimages = false; //if none of the subcategories have images, we don't display buttons with icons + for(var i = 0; i < this.subcategories.length; i++){ + if(this.subcategories[i].image){ + hasimages = true; + break; + } + } + + var list_container = el_node.querySelector('.category-list'); + if (list_container) { + if (!hasimages) { + list_container.classList.add('simple'); + } else { + list_container.classList.remove('simple'); + } + for(var i = 0, len = this.subcategories.length; i < len; i++){ + if(this.subcategories[i].available == true){ + list_container.appendChild(this.render_category(this.subcategories[i],hasimages)); + }else{ + var ancestor_ids = db.get_category_ancestors_ids(this.subcategories[i].id) + for(var j = 0, jlen = ancestor_ids.length; j < jlen; j++){ + if(ancestor_ids[j] != 0){ + var ancestor = db.get_category_by_id(ancestor_ids[j]) + if(ancestor.available == true){ + list_container.appendChild(this.render_category(this.subcategories[i],hasimages)); + } + } + } + } + + }; + } + + var buttons = el_node.querySelectorAll('.js-category-switch'); + for(var i = 0; i < buttons.length; i++){ + buttons[i].addEventListener('click',this.switch_category_handler); + } + + var products = this.pos.db.get_product_by_category(this.category.id); + var available_products = []; + for(var i = 0, len = products.length; i < len; i++){ + if(products[i].available == true){ + available_products.push(products[i]); + } + } + this.product_list_widget.set_product_list(available_products); + + this.el.querySelector('.searchbox input').addEventListener('keyup',this.search_handler); + $('.searchbox input', this.el).keypress(function(e){ + e.stopPropagation(); + }); + + this.el.querySelector('.search-clear').addEventListener('click',this.clear_search_handler); + + if(this.pos.config.iface_vkeyboard && this.pos_widget.onscreen_keyboard){ + this.pos_widget.onscreen_keyboard.connect($(this.el.querySelector('.searchbox input'))); + } + }, + }); +}; diff --git a/pos_product_category_filter/views/custom_view.xml b/pos_product_category_filter/views/custom_view.xml new file mode 100644 index 000000000..0ad88241e --- /dev/null +++ b/pos_product_category_filter/views/custom_view.xml @@ -0,0 +1,18 @@ + + + + + + pos.config.form.view.inherit + pos.config + + + + + + + + + + + \ No newline at end of file diff --git a/pos_product_category_filter/views/template.xml b/pos_product_category_filter/views/template.xml new file mode 100644 index 000000000..9802f7b56 --- /dev/null +++ b/pos_product_category_filter/views/template.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/sale_discount_total/__init__.py b/sale_discount_total/__init__.py index 2c4eac3f8..158fa9056 100644 --- a/sale_discount_total/__init__.py +++ b/sale_discount_total/__init__.py @@ -1 +1,2 @@ -import models \ No newline at end of file +import models +import report diff --git a/sale_discount_total/__openerp__.py b/sale_discount_total/__openerp__.py index a3c8f2194..de16d9850 100644 --- a/sale_discount_total/__openerp__.py +++ b/sale_discount_total/__openerp__.py @@ -3,7 +3,7 @@ 'version': '1.0', 'category': 'sale', 'sequence': 6, - 'summary': "extension of default Sale Management module meant to provide discount for total amount", + 'summary': "A module meant to provide discount for total amount and Discount limit with approval in sales", 'author': 'Cybrosys Techno Solutions', 'company': 'Cybrosys Techno Solutions', 'website': 'http://www.cybrosys.com', @@ -21,8 +21,10 @@ Module to manage discount for total amount in Sale. 'data': [ 'views/sale_view.xml', 'views/account_invoice_view.xml', - 'views/invoice_report.xml', - 'views/sale_order_report.xml' + 'views/invoice_report.xml', + 'views/sale_order_report.xml', + 'views/sale_discount_approval_view.xml', + 'views/sale_discount_approval_workflow.xml' ], 'demo': [ diff --git a/sale_discount_total/__openerp__.py~ b/sale_discount_total/__openerp__.py~ index 6906c9c41..d0b5a7ac0 100644 --- a/sale_discount_total/__openerp__.py~ +++ b/sale_discount_total/__openerp__.py~ @@ -20,7 +20,11 @@ Module to manage discount for total amount in Sale. 'depends': ['sale', 'base', 'stock'], 'data': [ 'views/sale_view.xml', - 'views/account_invoice_view.xml' + 'views/account_invoice_view.xml', + 'views/invoice_report.xml', + 'views/sale_order_report.xml', + 'views/sale_discount_approval_view.xml', + 'views/sale_discount_approval_workflow.xml' ], 'demo': [ diff --git a/sale_discount_total/models/__init__.py b/sale_discount_total/models/__init__.py index aa35028cb..16025a4ed 100644 --- a/sale_discount_total/models/__init__.py +++ b/sale_discount_total/models/__init__.py @@ -1,2 +1,3 @@ import account_invoice import sale +import sale_discount_approval diff --git a/sale_discount_total/models/__init__.py~ b/sale_discount_total/models/__init__.py~ new file mode 100644 index 000000000..aa35028cb --- /dev/null +++ b/sale_discount_total/models/__init__.py~ @@ -0,0 +1,2 @@ +import account_invoice +import sale diff --git a/sale_discount_total/models/account_invoice.py b/sale_discount_total/models/account_invoice.py index 6912f4c87..257a202b8 100644 --- a/sale_discount_total/models/account_invoice.py +++ b/sale_discount_total/models/account_invoice.py @@ -3,25 +3,38 @@ from openerp.osv import osv import openerp.addons.decimal_precision as dp +class AccountInvoiceLine(models.Model): + _inherit = "account.invoice.line" + + discount = fields.Float(string='Discount (%)', + digits=(16, 10), + # digits= dp.get_precision('Discount'), + default=0.0) + class AccountInvoice(models.Model): _inherit = "account.invoice" @api.one @api.depends('invoice_line.price_subtotal', 'tax_line.amount') def _compute_amount(self): + disc = 0.0 + for inv in self: + for line in inv.invoice_line: + print line.discount + disc += (line.quantity * line.price_unit) * line.discount / 100 self.amount_untaxed = sum(line.price_subtotal for line in self.invoice_line) self.amount_tax = sum(line.amount for line in self.tax_line) - if self.discount_type == 'percent': - self.amount_discount = ((self.amount_untaxed + self.amount_tax) * self.discount_rate) / 100 - elif self.discount_type == 'amount': - self.amount_discount = self.discount_rate - self.amount_total = self.amount_untaxed + self.amount_tax - self.amount_discount + self.amount_discount = disc + self.amount_total = self.amount_untaxed + self.amount_tax discount_type = fields.Selection([('percent', 'Percentage'), ('amount', 'Amount')], 'Discount Type', readonly=True, states={'draft': [('readonly', False)]}) - discount_rate = fields.Float('Discount Rate', digits_compute=dp.get_precision('Account'), readonly=True, + discount_rate = fields.Float('Discount Rate', + digits_compute=dp.get_precision('Account'), + readonly=True, states={'draft': [('readonly', False)]}) - amount_discount = fields.Float(string='Discount', digits=dp.get_precision('Account'), + amount_discount = fields.Float(string='Discount', + digits=dp.get_precision('Account'), readonly=True, compute='_compute_amount') amount_untaxed = fields.Float(string='Subtotal', digits=dp.get_precision('Account'), readonly=True, compute='_compute_amount', track_visibility='always') @@ -30,23 +43,40 @@ class AccountInvoice(models.Model): amount_total = fields.Float(string='Total', digits=dp.get_precision('Account'), readonly=True, compute='_compute_amount') + @api.multi + def compute_discount(self, discount): + for inv in self: + val1 = val2 = 0.0 + disc_amnt = 0.0 + val2 = sum(line.amount for line in self.tax_line) + for line in inv.invoice_line: + val1 += (line.quantity * line.price_unit) + line.discount = discount + disc_amnt += (line.quantity * line.price_unit) * discount / 100 + total = val1 + val2 - disc_amnt + self.amount_discount = disc_amnt + self.amount_tax = val2 + self.amount_total = total + @api.onchange('discount_type', 'discount_rate') - def compute_discount(self): + def supply_rate(self): for inv in self: amount = sum(line.price_subtotal for line in self.invoice_line) tax = sum(line.amount for line in self.tax_line) if inv.discount_type == 'percent': - if inv.discount_rate == 100: - disc_amnt = amount + tax - else: - disc_amnt = (amount + tax) * inv.discount_rate / 100 - total = amount + tax - disc_amnt - self.amount_discount = disc_amnt - self.amount_total = total + self.compute_discount(inv.discount_rate) else: - total = (amount + tax) - inv.discount_rate - self.amount_discount = inv.discount_rate - self.amount_total = total + total = 0.0 + discount = 0.0 + for line in inv.invoice_line: + total += (line.quantity * line.price_unit) + if inv.discount_rate != 0: + print "#########################################" + discount = (inv.discount_rate / total) * 100 + print dp.get_precision('Discount'), + print discount + self.compute_discount(discount) + @api.model def _prepare_refund(self, invoice, date=None, period_id=None, description=None, journal_id=None): @@ -58,31 +88,3 @@ class AccountInvoice(models.Model): }) return res - -class invoice_line(osv.Model): - _inherit = 'account.invoice.line' - - def move_line_get(self, cr, uid, invoice_id, context=None): - res = super(invoice_line, self).move_line_get(cr, uid, invoice_id, context=context) - inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id, context=context) - - if inv.type in ('out_invoice', 'out_refund') and inv.discount_type: - prop = self.pool.get('ir.property').get(cr, uid, 'property_account_income_categ', 'product.category', - context=context) - prop_id = prop and prop.id or False - account_id = self.pool.get('account.fiscal.position').map_account(cr, uid, inv.fiscal_position or False, - prop_id) - sign = -1 - - res.append({ - 'name': 'Discount', - 'price_unit': sign * inv.amount_discount, - 'quantity': 1, - 'price': sign * inv.amount_discount, - 'account_id': account_id, - 'product_id': False, - 'uos_id': False, - 'account_analytic_id': False, - 'taxes': False, - }) - return res diff --git a/sale_discount_total/models/sale.py b/sale_discount_total/models/sale.py index d07f29394..e68d28d39 100644 --- a/sale_discount_total/models/sale.py +++ b/sale_discount_total/models/sale.py @@ -16,20 +16,16 @@ class SaleOrder(osv.Model): 'amount_tax': 0.0, 'amount_total': 0.0, } - val = val1 = val2 = 0.0 cur = order.pricelist_id.currency_id + val1 = val2 = val3 = 0.0 for line in order.order_line: val1 += line.price_subtotal - val += self._amount_line_tax(cr, uid, line, context=context) - if order.discount_type == 'amount': - val2 = order.discount_rate - elif order.discount_type == 'percent': - val2 = ((val1 + val) * order.discount_rate) / 100 - 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_discount'] = cur_obj.round(cr, uid, cur, val2) - res[order.id]['amount_total'] = res[order.id]['amount_untaxed'] + res[order.id]['amount_tax'] - \ - res[order.id]['amount_discount'] + val2 += self._amount_line_tax(cr, uid, line, context=context) + val3 += (line.product_uom_qty * line.price_unit) * line.discount / 100 + res[order.id]['amount_untaxed'] = round(cur_obj.round(cr, uid, cur, val1)) + res[order.id]['amount_tax'] = round(cur_obj.round(cr, uid, cur, val2)) + res[order.id]['amount_discount'] = round(cur_obj.round(cr, uid, cur, val3)) + res[order.id]['amount_total'] = round(res[order.id]['amount_untaxed'] + res[order.id]['amount_tax']) return res def _get_order(self, cr, uid, ids, context=None): @@ -41,46 +37,52 @@ class SaleOrder(osv.Model): _columns = { 'discount_type': fields.selection([ ('percent', 'Percentage'), - ('amount', 'Amount')], 'Discount Type'), + ('amount', 'Amount')], 'Discount type'), 'discount_rate': fields.float('Discount Rate', digits_compute=dp.get_precision('Account'), readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, ), 'amount_discount': fields.function(_amount_all, digits_compute=dp.get_precision('Account'), string='Discount', - multi='sums', help="The total discount."), + multi='sums', store=True, help="The total discount."), 'amount_untaxed': fields.function(_amount_all, digits_compute=dp.get_precision('Account'), string='Untaxed Amount', - multi='sums', help="The amount without tax.", track_visibility='always'), + multi='sums', store=True, help="The amount without tax.", track_visibility='always'), 'amount_tax': fields.function(_amount_all, digits_compute=dp.get_precision('Account'), string='Taxes', - multi='sums', help="The tax amount."), + multi='sums', store=True, help="The tax amount."), 'amount_total': fields.function(_amount_all, digits_compute=dp.get_precision('Account'), string='Total', - multi='sums', help="The total amount."), + multi='sums', store=True, help="The total amount."), } _defaults = { 'discount_type': 'percent', } - @api.onchange('discount_type', 'discount_rate') - def compute_discount(self): + @api.multi + def compute_discount(self, discount): for order in self: val1 = val2 = 0.0 + disc_amnt = 0.0 for line in order.order_line: - val1 += line.price_subtotal + val1 += (line.product_uom_qty * line.price_unit) + line.discount = discount val2 += self._amount_line_tax(line) + disc_amnt += (line.product_uom_qty * line.price_unit * line.discount)/100 + total = val1 + val2 - disc_amnt + self.currency_id = order.pricelist_id.currency_id + self.amount_discount = round(disc_amnt) + self.amount_tax = round(val2) + self.amount_total = round(total) + + @api.onchange('discount_type', 'discount_rate') + def supply_rate(self): + for order in self: if order.discount_type == 'percent': - if order.discount_rate == 100: - disc_amnt = val1 + val2 - else: - disc_amnt = (val1 + val2) * order.discount_rate / 100 - total = val1 + val2 - disc_amnt - self.currency_id = order.pricelist_id.currency_id - self.amount_discount = disc_amnt - self.amount_total = total + self.compute_discount(order.discount_rate) else: - total = (val1 + val2) - order.discount_rate - self.currency_id = order.pricelist_id.currency_id - self.amount_discount = order.discount_rate - self.amount_total = total + total = 0.0 + for line in order.order_line: + total += (line.product_uom_qty * line.price_unit) + discount = (order.discount_rate / total) * 100 + self.compute_discount(discount) def _prepare_invoice(self, cr, uid, order, lines, context=None): invoice_vals = super(SaleOrder, self)._prepare_invoice(cr, uid, order, lines, context=context) diff --git a/sale_discount_total/models/sale_discount_approval.py b/sale_discount_total/models/sale_discount_approval.py new file mode 100644 index 000000000..409411bb6 --- /dev/null +++ b/sale_discount_total/models/sale_discount_approval.py @@ -0,0 +1,102 @@ +from openerp import api, models, fields +from openerp.osv import fields, osv +from openerp import SUPERUSER_ID + +##############################################################sale settings############################################################## + +class Sale_config_settings(osv.TransientModel): + _inherit = 'sale.config.settings' + _columns = { + 'limit_discount': fields.integer('Discount limit requires approval %', required=True, + help="Discount after which approval of sale is required."), + 'module_sale_discount_approval': fields.boolean("Force two levels of approvals", + help='Provide a double validation mechanism for sale exceeding minimum discount.\n' + ), + } + + _defaults = { + 'limit_discount': 40, + } + + def get_default_limit_discount(self, cr, uid, ids, context=None): + ir_values = self.pool.get('ir.values') + limit_discount = ir_values.get_default(cr, uid, 'sale.config.settings', 'limit_discount') + return { + 'limit_discount': limit_discount, + } + + def set_limit_discount(self, cr, uid, ids, context=None): + ir_values = self.pool.get('ir.values') + wizard = self.browse(cr, uid, ids)[0] + if wizard.limit_discount: + limit_discount = wizard.limit_discount + ir_values.set_default(cr, SUPERUSER_ID, 'sale.config.settings', 'limit_discount', limit_discount) + + def get_default_module_sale_discount_approval(self, cr, uid, ids, context=None): + ir_values = self.pool.get('ir.values') + module_sale_discount_approval = ir_values.get_default(cr, uid, 'sale.config.settings', + 'module_sale_discount_approval') + return { + 'module_sale_discount_approval': module_sale_discount_approval == 'True', + } + + def set_module_sale_discount_approval(self, cr, uid, ids, context=None): + ir_values = self.pool.get('ir.values') + wizard = self.browse(cr, uid, ids)[0] + if wizard.module_sale_discount_approval: + module_sale_discount_approval = 'True' + else: + module_sale_discount_approval = 'False' + + ir_values.set_default(cr, SUPERUSER_ID, 'sale.config.settings', 'module_sale_discount_approval', + module_sale_discount_approval) + + +#######################################################sale order workflow############################################################## + +class SaleInherit(osv.Model): + _inherit = 'sale.order' + + _columns = { + 'state': fields.selection([('draft', 'Draft Quotation'), + ('sent', 'Quotation Sent'), + ('cancel', 'Cancelled'), + ('waiting_date', 'Waiting Schedule'), + ('waitingapproval', 'Waiting Approval'), + ('progress', 'Sales Order'), + ('manual', 'Sale to Invoice'), + ('shipping_except', 'Shipping Exception'), + ('invoice_except', 'Invoice Exception'), + ('done', 'Done')], required=True, track_visibility='onchange'), + } + + def action_button_confirm(self, cr, uid, ids, context=None): + discnt = 0.0 + no_line = 0.0 + line_dicnt = 0.0 + prod_price = 0.0 + conf = self.pool.get('ir.values') + sale_obj = self.browse(cr, uid, ids, context) + double_valid = conf.get_default(cr, uid, 'sale.config.settings', 'module_sale_discount_approval') + if double_valid == 'True': + min_disct = conf.get_default(cr, uid, 'sale.config.settings', 'limit_discount') + for line in sale_obj.order_line: + no_line += 1 + discnt += line.discount + discnt = (discnt / no_line) + if discnt >= min_disct: + assert len(ids) == 1, 'This option should only be used for a single id at a time.' + self.signal_workflow(cr, uid, ids, 'order_toapprov') + return True + else: + return super(SaleInherit, self).action_button_confirm(cr, uid, ids, context) + else: + return super(SaleInherit, self).action_button_confirm(cr, uid, ids, context) + + ####################################### workflow functions############################################################################# + + @api.one + def wait_approval(self): + + self.state = 'waitingapproval' + return True diff --git a/sale_discount_total/report/__init__.py b/sale_discount_total/report/__init__.py new file mode 100644 index 000000000..998ed8390 --- /dev/null +++ b/sale_discount_total/report/__init__.py @@ -0,0 +1,2 @@ +import sale_report +import invoice_report \ No newline at end of file diff --git a/sale_discount_total/report/invoice_report.py b/sale_discount_total/report/invoice_report.py new file mode 100644 index 000000000..352d533b0 --- /dev/null +++ b/sale_discount_total/report/invoice_report.py @@ -0,0 +1,22 @@ +from openerp.osv import fields, osv + + +class DiscountInvoiceReport(osv.osv): + _inherit = 'account.invoice.report' + + _columns = { + 'discount': fields.float('Discount', readonly=True), + } + + def _select(self): + res = super(DiscountInvoiceReport,self)._select() + select_str = res + """, sub.discount / cr.rate as discount """ + return select_str + + def _sub_select(self): + res = super(DiscountInvoiceReport,self)._sub_select() + select_str = res + """,SUM(CASE + WHEN ai.type::text = ANY (ARRAY['out_refund'::character varying::text, 'in_invoice'::character varying::text]) + THEN - ((ail.quantity / u.factor * u2.factor) * ail.price_unit * (ail.discount) / 100.0) + ELSE ((ail.quantity / u.factor * u2.factor) * ail.price_unit * (ail.discount) / 100.0) END) as discount""" + return select_str diff --git a/sale_discount_total/report/sale_report.py b/sale_discount_total/report/sale_report.py new file mode 100644 index 000000000..29b95b06f --- /dev/null +++ b/sale_discount_total/report/sale_report.py @@ -0,0 +1,14 @@ +from openerp.osv import fields, osv + + +class DiscountSaleReport(osv.osv): + _inherit = 'sale.report' + + _columns = { + 'discount': fields.float('Discount', readonly=True), + } + + def _select(self): + res = super(DiscountSaleReport,self)._select() + select_str = res+""",sum(l.product_uom_qty * cr.rate * l.price_unit * (l.discount) / 100.0) as discount""" + return select_str diff --git a/sale_discount_total/static/description/Disc_appr_conf.png b/sale_discount_total/static/description/Disc_appr_conf.png new file mode 100644 index 000000000..cde05d9c3 Binary files /dev/null and b/sale_discount_total/static/description/Disc_appr_conf.png differ diff --git a/sale_discount_total/static/description/Disc_appr_wrkfl.png b/sale_discount_total/static/description/Disc_appr_wrkfl.png new file mode 100644 index 000000000..e54e5aa78 Binary files /dev/null and b/sale_discount_total/static/description/Disc_appr_wrkfl.png differ diff --git a/sale_discount_total/static/description/Discount_inv_amnt.png b/sale_discount_total/static/description/Discount_inv_amnt.png new file mode 100644 index 000000000..c02be021a Binary files /dev/null and b/sale_discount_total/static/description/Discount_inv_amnt.png differ diff --git a/sale_discount_total/static/description/Discount_so_perc.png b/sale_discount_total/static/description/Discount_so_perc.png new file mode 100644 index 000000000..ea947ef0c Binary files /dev/null and b/sale_discount_total/static/description/Discount_so_perc.png differ diff --git a/sale_discount_total/static/description/index.html b/sale_discount_total/static/description/index.html new file mode 100644 index 000000000..708815aeb --- /dev/null +++ b/sale_discount_total/static/description/index.html @@ -0,0 +1,39 @@ +
+
+

Discount On Sale

+ +
+

+ This module allows you to mention discount on Total of sale order and Total of Customer Invoice in two ways +

+
+

+ 1. As percentage
+ Select 'Percentage' from Discount type and give discount percentage as Discount rate. + System will update the value of Discount and Total +

+
+ +
+
+

+ 2. As amount
+ Select 'Amount' from Discount type and give discount amount as Discount rate. + System will update the value of Discount and Total +

+
+ +
+
+

+ And the module also allows you to set a limit for total discount in percentage. Exceeding this limit + will require approval. +

+
+
+ +
+
+ +
+
diff --git a/sale_discount_total/views/account_invoice_view.xml b/sale_discount_total/views/account_invoice_view.xml index 718327b7b..4a4116452 100644 --- a/sale_discount_total/views/account_invoice_view.xml +++ b/sale_discount_total/views/account_invoice_view.xml @@ -1,11 +1,36 @@ + + discount.account.invoice.line.tree + account.invoice.line + + + + (16, 2) + + + + + + discount.account.invoice.line.form + account.invoice.line + + + + (16, 2) + + + + discount.account.invoice account.invoice + + (16, 2) + @@ -14,14 +39,14 @@ -
-
+ - diff --git a/sale_discount_total/views/invoice_report.xml b/sale_discount_total/views/invoice_report.xml index 721181872..623e9d606 100644 --- a/sale_discount_total/views/invoice_report.xml +++ b/sale_discount_total/views/invoice_report.xml @@ -9,11 +9,9 @@ Description Quantity Unit Price - - - Discount (%) - - + + Discount (%) + Taxes Amount @@ -28,8 +26,11 @@ - - + + + + + @@ -43,41 +44,41 @@
- - + --> + Discount - + - - - - -
- - - - - - - - - - - -
Discount Type - -
Discount rate - -
-
-
+ + + + + + + + + + + + + + + + + + + + + + +
-
\ No newline at end of file + diff --git a/sale_discount_total/views/invoice_report.xml~ b/sale_discount_total/views/invoice_report.xml~ new file mode 100644 index 000000000..08742101d --- /dev/null +++ b/sale_discount_total/views/invoice_report.xml~ @@ -0,0 +1,87 @@ + + + + + + \ No newline at end of file diff --git a/sale_discount_total/views/sale_discount_approval_view.xml b/sale_discount_total/views/sale_discount_approval_view.xml new file mode 100644 index 000000000..ec02ac63d --- /dev/null +++ b/sale_discount_total/views/sale_discount_approval_view.xml @@ -0,0 +1,28 @@ + + + + + + Sale Application + sale.config.settings + + + + +
+
+ +
+ +
+
+
+
+ +
+
diff --git a/sale_discount_total/views/sale_discount_approval_workflow.xml b/sale_discount_total/views/sale_discount_approval_workflow.xml new file mode 100644 index 000000000..84631a160 --- /dev/null +++ b/sale_discount_total/views/sale_discount_approval_workflow.xml @@ -0,0 +1,133 @@ + + + + + + + sale.order.workflow.inherit + sale.order + + + + + + + + +