codepush
This commit is contained in:
commit
70dda814aa
15
bahmni-addons/LICENSE
Normal file
15
bahmni-addons/LICENSE
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
Additional modules for Odoo for Bahmni specific integrations
|
||||||
|
Copyright (C) 2014 OpenMRS, Inc
|
||||||
|
|
||||||
|
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 <http://www.gnu.org/licenses/>.
|
2
bahmni-addons/NOTICE
Normal file
2
bahmni-addons/NOTICE
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Copyright (C) 2013 OpenMRS, Inc
|
||||||
|
This product includes software developed under the stewardship of the Bahmni Coalition, under fiscal sponsorship of OpenMRS, Inc. (http://www.openmrs.org/)
|
2
bahmni-addons/README.md
Normal file
2
bahmni-addons/README.md
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# odoo-modules
|
||||||
|
Custom Odoo modules (extensions) for Bahmni
|
3
bahmni-addons/bahmni_account/__init__.py
Normal file
3
bahmni-addons/bahmni_account/__init__.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import models
|
||||||
|
import report
|
30
bahmni-addons/bahmni_account/__manifest__.py
Normal file
30
bahmni-addons/bahmni_account/__manifest__.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
{
|
||||||
|
'name': 'Bahmni Account',
|
||||||
|
'version': '1.0',
|
||||||
|
'summary': 'Custom account module to meet bahmni requirement',
|
||||||
|
'sequence': 1,
|
||||||
|
'description': """
|
||||||
|
Bahmni Account
|
||||||
|
====================
|
||||||
|
""",
|
||||||
|
'category': 'Account',
|
||||||
|
'website': '',
|
||||||
|
'images': [],
|
||||||
|
'depends': ['account', 'account_voucher', 'web_readonly_bypass'],
|
||||||
|
'data': [
|
||||||
|
'views/bahmni_account.xml',
|
||||||
|
'views/account_invoice_view.xml',
|
||||||
|
'views/account_config_settings.xml',
|
||||||
|
'report/account_count_report.xml',
|
||||||
|
'report/account_report.xml',
|
||||||
|
'security/ir.model.access.csv',
|
||||||
|
'views/account_payment.xml',
|
||||||
|
'report/report_invoice_inherit.xml'
|
||||||
|
],
|
||||||
|
'demo': [],
|
||||||
|
'qweb': [],
|
||||||
|
'installable': True,
|
||||||
|
'application': True,
|
||||||
|
'auto_install': False,
|
||||||
|
}
|
13
bahmni-addons/bahmni_account/data/discount_accounts.xml
Normal file
13
bahmni-addons/bahmni_account/data/discount_accounts.xml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<odoo>
|
||||||
|
<!--Account for debiting discount amount-->
|
||||||
|
<record id="account_discount" model="account.account">
|
||||||
|
<field name="name">Discount</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!--Account for debiting amount which is paid extra-->
|
||||||
|
<record id="account_overcharge" model="account.account">
|
||||||
|
<field name="name">Overcharge</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
19
bahmni-addons/bahmni_account/doc/ChangeLog.txt
Normal file
19
bahmni-addons/bahmni_account/doc/ChangeLog.txt
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
1. New fields added to account.invoice for applying discounts on invoices.
|
||||||
|
1. Discount Type : None, Fixed, Percentage
|
||||||
|
2. Discount : Total discount amount getting applied.
|
||||||
|
3. Discount Percentage : If percentage type is selected, then for defining percentage.
|
||||||
|
- if discount type is selected as percentage, then discount field will be readonly,
|
||||||
|
as discount amount will get computed based on given percentage.
|
||||||
|
4. Discount Account Head : Account from/to which discount amount will get credited or debited.
|
||||||
|
2. on change method is defined for discount percentage and invoice lines, to compute discount amount, when type percetage is selected.
|
||||||
|
3. Customer Invoices form is inherited, and footer is defined completely in new way,
|
||||||
|
as default footer class was not making form look good.
|
||||||
|
4. compute_amount method is overridden to change the logic of computing total of invoice, i.e. deduct discount amount from total.
|
||||||
|
5. create method of account_invoice_line is inherited to set value of discount field,
|
||||||
|
when invoice is created through Create Invoice button on Sale Order form.
|
||||||
|
As method which is getting called through that button, is creating invoice object first
|
||||||
|
and then one-by-one invoice lines are getting created.
|
||||||
|
6. rouding.off class defined to set round_off_amount value in account_invoice and sale_order.
|
||||||
|
for calculating this value, configuration is provided to user under Sales > Configuration > Settings menu,
|
||||||
|
where user can set Round Off Value, this is the amount to which value has to get rounded off.
|
||||||
|
|
3
bahmni-addons/bahmni_account/doc/imp_points.txt
Normal file
3
bahmni-addons/bahmni_account/doc/imp_points.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
* rounding.off class is kept in bahmni_account module, as it is used in account_invoice and sale_order class.
|
||||||
|
bahmni_sale already has dependency of bahmni_account module, hence this class can be accessed in that module.
|
||||||
|
vice-versa won't happen; as it will be a round dependency.
|
7
bahmni-addons/bahmni_account/models/__init__.py
Normal file
7
bahmni-addons/bahmni_account/models/__init__.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import rounding_off
|
||||||
|
import account_invoice
|
||||||
|
import account_invoice_line
|
||||||
|
import account_config_settings
|
||||||
|
import res_company
|
||||||
|
import account_payment
|
BIN
bahmni-addons/bahmni_account/models/__init__.pyc
Normal file
BIN
bahmni-addons/bahmni_account/models/__init__.pyc
Normal file
Binary file not shown.
@ -0,0 +1,15 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from odoo import models, fields, api
|
||||||
|
|
||||||
|
|
||||||
|
class AccountConfigSettings(models.TransientModel):
|
||||||
|
_inherit = 'account.config.settings'
|
||||||
|
|
||||||
|
round_off_by = fields.Float(string="Round off by", related="company_id.round_off_by")
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def set_round_off_by_defaults(self):
|
||||||
|
return self.env['ir.values'].sudo().set_default(
|
||||||
|
'account.config.settings', 'round_off_by', self.round_off_by)
|
||||||
|
|
||||||
|
|
BIN
bahmni-addons/bahmni_account/models/account_config_settings.pyc
Normal file
BIN
bahmni-addons/bahmni_account/models/account_config_settings.pyc
Normal file
Binary file not shown.
286
bahmni-addons/bahmni_account/models/account_invoice.py
Normal file
286
bahmni-addons/bahmni_account/models/account_invoice.py
Normal file
@ -0,0 +1,286 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from odoo import fields, models, api, _
|
||||||
|
from odoo.exceptions import UserError, ValidationError
|
||||||
|
import logging
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
# mapping invoice type to refund type
|
||||||
|
TYPE2REFUND = {
|
||||||
|
'out_invoice': 'out_refund', # Customer Invoice
|
||||||
|
'in_invoice': 'in_refund', # Vendor Bill
|
||||||
|
'out_refund': 'out_invoice', # Customer Refund
|
||||||
|
'in_refund': 'in_invoice', # Vendor Refund
|
||||||
|
}
|
||||||
|
|
||||||
|
class AccountInvoice(models.Model):
|
||||||
|
_inherit = 'account.invoice'
|
||||||
|
|
||||||
|
# # overridden this method to deduct discounted amount from total of invoice
|
||||||
|
@api.one
|
||||||
|
@api.depends('invoice_line_ids.price_subtotal', 'tax_line_ids.amount',
|
||||||
|
'currency_id', 'company_id', 'date_invoice', 'type', 'discount')
|
||||||
|
def _compute_amount(self):
|
||||||
|
round_curr = self.currency_id.round
|
||||||
|
self.amount_untaxed = sum(line.price_subtotal for line in self.invoice_line_ids)
|
||||||
|
self.amount_tax = sum(round_curr(line.amount) for line in self.tax_line_ids)
|
||||||
|
amount_total = self.amount_untaxed + self.amount_tax - self.discount
|
||||||
|
self.round_off_amount = self.env['rounding.off'].round_off_value_to_nearest(amount_total)
|
||||||
|
self.amount_total = self.amount_untaxed + self.amount_tax - self.discount + self.round_off_amount
|
||||||
|
amount_total_company_signed = self.amount_total
|
||||||
|
amount_untaxed_signed = self.amount_untaxed
|
||||||
|
if self.currency_id and self.company_id and self.currency_id != self.company_id.currency_id:
|
||||||
|
currency_id = self.currency_id.with_context(date=self.date_invoice)
|
||||||
|
amount_total_company_signed = currency_id.compute(self.amount_total, self.company_id.currency_id)
|
||||||
|
amount_untaxed_signed = currency_id.compute(self.amount_untaxed, self.company_id.currency_id)
|
||||||
|
sign = self.type in ['in_refund', 'out_refund'] and -1 or 1
|
||||||
|
self.amount_total_company_signed = amount_total_company_signed * sign
|
||||||
|
self.amount_total_signed = self.amount_total * sign
|
||||||
|
self.amount_untaxed_signed = amount_untaxed_signed * sign
|
||||||
|
|
||||||
|
discount_type = fields.Selection([('none', 'No Discount'),
|
||||||
|
('fixed', 'Fixed'),
|
||||||
|
('percentage', 'Percentage')],
|
||||||
|
string="Discount Method",
|
||||||
|
default='none')
|
||||||
|
discount = fields.Monetary(string="Discount")
|
||||||
|
discount_percentage = fields.Float(string="Discount Percentage")
|
||||||
|
disc_acc_id = fields.Many2one('account.account',
|
||||||
|
string="Discount Account Head")
|
||||||
|
round_off_amount = fields.Monetary(string="Round Off Amount",
|
||||||
|
compute=_compute_amount)
|
||||||
|
|
||||||
|
@api.onchange('invoice_line_ids')
|
||||||
|
def onchange_invoice_lines(self):
|
||||||
|
amount_total = self.amount_untaxed + self.amount_tax
|
||||||
|
if self.discount_type == 'fixed':
|
||||||
|
self.discount_percentage = (self.discount / amount_total) * 100
|
||||||
|
elif self.discount_type == 'percentage':
|
||||||
|
self.discount = amount_total * self.discount_percentage / 100
|
||||||
|
|
||||||
|
@api.onchange('discount', 'discount_percentage', 'discount_type')
|
||||||
|
def onchange_discount(self):
|
||||||
|
amount_total = self.amount_untaxed + self.amount_tax
|
||||||
|
if self.discount:
|
||||||
|
self.discount_percentage = (self.discount / amount_total) * 100
|
||||||
|
if self.discount_percentage:
|
||||||
|
self.discount = amount_total * self.discount_percentage / 100
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def _find_batch(self, product, qty, location, picking):
|
||||||
|
_logger.info("\n\n***** Product :%s, Quantity :%s Location :%s\n*****",product,qty,location)
|
||||||
|
lot_objs = self.env['stock.production.lot'].search([('product_id','=',product.id),('life_date','>=',str(fields.datetime.now()))])
|
||||||
|
_logger.info('\n *** Searched Lot Objects:%s \n',lot_objs)
|
||||||
|
if any(lot_objs):
|
||||||
|
#Sort losts based on the expiry date FEFO(First Expiry First Out)
|
||||||
|
lot_objs = list(lot_objs)
|
||||||
|
sorted_lot_list = sorted(lot_objs, key=lambda l: l.life_date)
|
||||||
|
_logger.info('\n *** Sorted based on FEFO :%s \n',sorted_lot_list)
|
||||||
|
done_qty = qty
|
||||||
|
res_lot_ids = []
|
||||||
|
lot_ids_for_query = tuple([lot.id for lot in sorted_lot_list])
|
||||||
|
self._cr.execute("SELECT SUM(qty) FROM stock_quant WHERE lot_id IN %s and location_id=%s",(lot_ids_for_query,location.id,))
|
||||||
|
qry_rslt = self._cr.fetchall()
|
||||||
|
available_qty = qry_rslt[0] and qry_rslt[0][0] or 0
|
||||||
|
if available_qty >= qty:
|
||||||
|
for lot_obj in sorted_lot_list:
|
||||||
|
quants = lot_obj.quant_ids.filtered(lambda q: q.location_id == location)
|
||||||
|
for quant in quants:
|
||||||
|
if done_qty >= 0:
|
||||||
|
res_lot_ids.append(lot_obj)
|
||||||
|
done_qty = done_qty - quant.qty
|
||||||
|
return res_lot_ids
|
||||||
|
else:
|
||||||
|
message = ("<b>Auto validation Failed</b> <br/> <b>Reason:</b> There are not enough stock available for <a href=# data-oe-model=product.product data-oe-id=%d>%s</a> product on <a href=# data-oe-model=stock.location data-oe-id=%d>%s</a> Location") % (product.id,product.name,location.id,location.name)
|
||||||
|
picking.message_post(body=message)
|
||||||
|
else:
|
||||||
|
message = ("<b>Auto validation Failed</b> <br/> <b>Reason:</b> There are no Batches/Serial no's available for <a href=# data-oe-model=product.product data-oe-id=%d>%s</a> product") % (product.id,product.name)
|
||||||
|
picking.message_post(body=message)
|
||||||
|
return False
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def action_move_create(self):
|
||||||
|
#This method is overriden to pass the Discount Journal Entry.
|
||||||
|
""" Creates invoice related analytics and financial move lines """
|
||||||
|
account_move = self.env['account.move']
|
||||||
|
|
||||||
|
for inv in self:
|
||||||
|
if not inv.journal_id.sequence_id:
|
||||||
|
raise UserError(_('Please define sequence on the journal related to this invoice.'))
|
||||||
|
if not inv.invoice_line_ids:
|
||||||
|
raise UserError(_('Please create some invoice lines.'))
|
||||||
|
if inv.move_id:
|
||||||
|
continue
|
||||||
|
|
||||||
|
ctx = dict(self._context, lang=inv.partner_id.lang)
|
||||||
|
|
||||||
|
if not inv.date_invoice:
|
||||||
|
inv.with_context(ctx).write({'date_invoice': fields.Date.context_today(self)})
|
||||||
|
company_currency = inv.company_id.currency_id
|
||||||
|
|
||||||
|
# create move lines (one per invoice line + eventual taxes and analytic lines)
|
||||||
|
iml = inv.invoice_line_move_line_get()
|
||||||
|
iml += inv.tax_line_move_line_get()
|
||||||
|
|
||||||
|
diff_currency = inv.currency_id != company_currency
|
||||||
|
# create one move line for the total and possibly adjust the other lines amount
|
||||||
|
total, total_currency, iml = inv.with_context(ctx).compute_invoice_totals(company_currency, iml)
|
||||||
|
|
||||||
|
name = inv.name or '/'
|
||||||
|
if inv.payment_term_id:
|
||||||
|
totlines = inv.with_context(ctx).payment_term_id.with_context(currency_id=company_currency.id).compute(total, inv.date_invoice)[0]
|
||||||
|
res_amount_currency = total_currency
|
||||||
|
ctx['date'] = inv._get_currency_rate_date()
|
||||||
|
for i, t in enumerate(totlines):
|
||||||
|
if inv.currency_id != company_currency:
|
||||||
|
amount_currency = company_currency.with_context(ctx).compute(t[1], inv.currency_id)
|
||||||
|
else:
|
||||||
|
amount_currency = False
|
||||||
|
|
||||||
|
# last line: add the diff
|
||||||
|
res_amount_currency -= amount_currency or 0
|
||||||
|
if i + 1 == len(totlines):
|
||||||
|
amount_currency += res_amount_currency
|
||||||
|
|
||||||
|
iml.append({
|
||||||
|
'type': 'dest',
|
||||||
|
'name': name,
|
||||||
|
'price': t[1],
|
||||||
|
'account_id': inv.account_id.id,
|
||||||
|
'date_maturity': t[0],
|
||||||
|
'amount_currency': diff_currency and amount_currency,
|
||||||
|
'currency_id': diff_currency and inv.currency_id.id,
|
||||||
|
'invoice_id': inv.id
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
iml.append({
|
||||||
|
'type': 'dest',
|
||||||
|
'name': name,
|
||||||
|
'price': total,
|
||||||
|
'account_id': inv.account_id.id,
|
||||||
|
'date_maturity': inv.date_due,
|
||||||
|
'amount_currency': diff_currency and total_currency,
|
||||||
|
'currency_id': diff_currency and inv.currency_id.id,
|
||||||
|
'invoice_id': inv.id
|
||||||
|
})
|
||||||
|
part = self.env['res.partner']._find_accounting_partner(inv.partner_id)
|
||||||
|
line = [(0, 0, self.line_get_convert(l, part.id)) for l in iml]
|
||||||
|
line = inv.group_lines(iml, line)
|
||||||
|
|
||||||
|
journal = inv.journal_id.with_context(ctx)
|
||||||
|
line = inv.finalize_invoice_move_lines(line)
|
||||||
|
date = inv.date or inv.date_invoice
|
||||||
|
move_vals = {
|
||||||
|
'ref': inv.reference,
|
||||||
|
'line_ids': line,
|
||||||
|
'journal_id': journal.id,
|
||||||
|
'date': date,
|
||||||
|
'narration': inv.comment,
|
||||||
|
}
|
||||||
|
ctx['company_id'] = inv.company_id.id
|
||||||
|
ctx['invoice'] = inv
|
||||||
|
ctx_nolang = ctx.copy()
|
||||||
|
ctx_nolang.pop('lang', None)
|
||||||
|
move = account_move.with_context(ctx_nolang).create(move_vals)
|
||||||
|
#=============Customized code starts=========
|
||||||
|
if inv.discount:
|
||||||
|
if inv.type == 'out_refund':
|
||||||
|
move_line = move.line_ids.filtered(lambda l:l.name==inv.name)
|
||||||
|
move_line.credit -= inv.discount
|
||||||
|
move_line_vals = {
|
||||||
|
'name':'Discount',
|
||||||
|
'company_id':move.company_id.id,
|
||||||
|
'account_id':inv.disc_acc_id.id,
|
||||||
|
'credit':inv.discount,
|
||||||
|
'date_maturity':date,
|
||||||
|
'currency_id': diff_currency and inv.currency_id.id,
|
||||||
|
'invoice_id': inv.id,
|
||||||
|
'partner_id':move_line.partner_id.id,
|
||||||
|
'move_id':move.id,
|
||||||
|
}
|
||||||
|
self.env['account.move.line'].create(move_line_vals)
|
||||||
|
|
||||||
|
else:
|
||||||
|
move_line = move.line_ids.filtered(lambda l:l.name=='/')
|
||||||
|
move_line.debit -= inv.discount
|
||||||
|
move_line_vals = {
|
||||||
|
'name':'Discount',
|
||||||
|
'company_id':move.company_id.id,
|
||||||
|
'account_id':inv.disc_acc_id.id,
|
||||||
|
'debit':inv.discount,
|
||||||
|
'date_maturity':date,
|
||||||
|
'currency_id': diff_currency and inv.currency_id.id,
|
||||||
|
'invoice_id': inv.id,
|
||||||
|
'partner_id':move_line.partner_id.id,
|
||||||
|
'move_id':move.id,
|
||||||
|
}
|
||||||
|
self.env['account.move.line'].create(move_line_vals)
|
||||||
|
#===========Customized code ends=============
|
||||||
|
# Pass invoice in context in method post: used if you want to get the same
|
||||||
|
# account move reference when creating the same invoice after a cancelled one:
|
||||||
|
move.post()
|
||||||
|
# make the invoice point to that move
|
||||||
|
vals = {
|
||||||
|
'move_id': move.id,
|
||||||
|
'date': date,
|
||||||
|
'move_name': move.name,
|
||||||
|
}
|
||||||
|
inv.with_context(ctx).write(vals)
|
||||||
|
return True
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _prepare_refund(self, invoice, date_invoice=None, date=None, description=None, journal_id=None):
|
||||||
|
""" Prepare the dict of values to create the new refund from the invoice.
|
||||||
|
This method may be overridden to implement custom
|
||||||
|
refund generation (making sure to call super() to establish
|
||||||
|
a clean extension chain).
|
||||||
|
|
||||||
|
:param record invoice: invoice to refund
|
||||||
|
:param string date_invoice: refund creation date from the wizard
|
||||||
|
:param integer date: force date from the wizard
|
||||||
|
:param string description: description of the refund from the wizard
|
||||||
|
:param integer journal_id: account.journal from the wizard
|
||||||
|
:return: dict of value to create() the refund
|
||||||
|
"""
|
||||||
|
values = {}
|
||||||
|
for field in self._get_refund_copy_fields():
|
||||||
|
if invoice._fields[field].type == 'many2one':
|
||||||
|
values[field] = invoice[field].id
|
||||||
|
else:
|
||||||
|
values[field] = invoice[field] or False
|
||||||
|
|
||||||
|
values['invoice_line_ids'] = self._refund_cleanup_lines(invoice.invoice_line_ids)
|
||||||
|
tax_lines = invoice.tax_line_ids
|
||||||
|
taxes_to_change = {
|
||||||
|
line.tax_id.id: line.tax_id.refund_account_id.id
|
||||||
|
for line in tax_lines.filtered(lambda l: l.tax_id.refund_account_id != l.tax_id.account_id)
|
||||||
|
}
|
||||||
|
cleaned_tax_lines = self._refund_cleanup_lines(tax_lines)
|
||||||
|
values['tax_line_ids'] = self._refund_tax_lines_account_change(cleaned_tax_lines, taxes_to_change)
|
||||||
|
|
||||||
|
if journal_id:
|
||||||
|
journal = self.env['account.journal'].browse(journal_id)
|
||||||
|
elif invoice['type'] == 'in_invoice':
|
||||||
|
journal = self.env['account.journal'].search([('type', '=', 'purchase')], limit=1)
|
||||||
|
else:
|
||||||
|
journal = self.env['account.journal'].search([('type', '=', 'sale')], limit=1)
|
||||||
|
values['journal_id'] = journal.id
|
||||||
|
|
||||||
|
values['type'] = TYPE2REFUND[invoice['type']]
|
||||||
|
values['date_invoice'] = date_invoice or fields.Date.context_today(invoice)
|
||||||
|
values['state'] = 'draft'
|
||||||
|
values['number'] = False
|
||||||
|
values['origin'] = invoice.number
|
||||||
|
values['payment_term_id'] = False
|
||||||
|
values['refund_invoice_id'] = invoice.id
|
||||||
|
#=============Customized code starts========= Added Custom discount fields in refund
|
||||||
|
values['discount_type'] = invoice.discount_type
|
||||||
|
values['discount'] = invoice.discount
|
||||||
|
values['discount_percentage'] = invoice.discount_percentage
|
||||||
|
values['disc_acc_id'] = invoice.disc_acc_id.id
|
||||||
|
#===========Customized code ends=============
|
||||||
|
|
||||||
|
if date:
|
||||||
|
values['date'] = date
|
||||||
|
if description:
|
||||||
|
values['name'] = description
|
||||||
|
return values
|
||||||
|
|
BIN
bahmni-addons/bahmni_account/models/account_invoice.pyc
Normal file
BIN
bahmni-addons/bahmni_account/models/account_invoice.pyc
Normal file
Binary file not shown.
43
bahmni-addons/bahmni_account/models/account_invoice_line.py
Normal file
43
bahmni-addons/bahmni_account/models/account_invoice_line.py
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from odoo import models, api, fields
|
||||||
|
|
||||||
|
|
||||||
|
class AccountInvoiceLine(models.Model):
|
||||||
|
_inherit = 'account.invoice.line'
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def create(self, vals):
|
||||||
|
'''This method is overridden to update discount amount in invoice,
|
||||||
|
when invoice is getting created from sale order, and discount type selected in sale order is percentage.
|
||||||
|
Since, discount amount in readonly field and it gets updated by onchange method, which won't get called when invoice is created from backend.'''
|
||||||
|
if vals.get('invoice_id'):
|
||||||
|
invoice_obj = self.env['account.invoice'].browse(vals.get('invoice_id'))
|
||||||
|
amount_untaxed = 0.0
|
||||||
|
amount_tax = 0.0
|
||||||
|
# calculating total from already created invoice lines.
|
||||||
|
for ln in invoice_obj.invoice_line_ids:
|
||||||
|
amount_untaxed += ln.price_subtotal
|
||||||
|
taxes = ln.invoice_line_tax_ids.compute_all(ln.price_subtotal, invoice_obj.currency_id,
|
||||||
|
ln.quantity, product=ln.product_id,
|
||||||
|
partner=invoice_obj.partner_shipping_id)
|
||||||
|
amount_tax += sum(t.get('amount', 0.0) for t in taxes.get('taxes', []))
|
||||||
|
if vals.get('invoice_line_tax_ids'):
|
||||||
|
price_unit = vals.get('price_unit') * vals.get('quantity')
|
||||||
|
if vals.get('discount'):
|
||||||
|
price_unit = price_unit * (1 - vals['discount']/100)
|
||||||
|
amount_untaxed += price_unit
|
||||||
|
tax_ids = []
|
||||||
|
if len(vals['invoice_line_tax_ids'][0]) == 3:
|
||||||
|
tax_ids = vals['invoice_line_tax_ids'][0][2]
|
||||||
|
elif len(vals['invoice_line_tax_ids'][0]) == 1:
|
||||||
|
tax_ids = vals['invoice_line_tax_ids'][0]
|
||||||
|
tax_obj = self.env['account.tax'].browse(tax_ids)
|
||||||
|
taxes = tax_obj.compute_all(price_unit, invoice_obj.currency_id,
|
||||||
|
vals.get('quantity'), product=vals.get('product_id'),
|
||||||
|
partner=invoice_obj.partner_id)
|
||||||
|
amount_tax += sum(t.get('amount', 0.0) for t in taxes.get('taxes', []))
|
||||||
|
if invoice_obj.discount_type == 'percentage':
|
||||||
|
discount_amount = (invoice_obj.currency_id.round(amount_untaxed) +
|
||||||
|
invoice_obj.currency_id.round(amount_tax)) * invoice_obj.discount_percentage / 100
|
||||||
|
invoice_obj.write({'discount': discount_amount})
|
||||||
|
return super(AccountInvoiceLine, self).create(vals)
|
BIN
bahmni-addons/bahmni_account/models/account_invoice_line.pyc
Normal file
BIN
bahmni-addons/bahmni_account/models/account_invoice_line.pyc
Normal file
Binary file not shown.
56
bahmni-addons/bahmni_account/models/account_payment.py
Normal file
56
bahmni-addons/bahmni_account/models/account_payment.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from itertools import groupby
|
||||||
|
from odoo import models, fields, api
|
||||||
|
|
||||||
|
|
||||||
|
class AccountAbstractPayment(models.AbstractModel):
|
||||||
|
_inherit = 'account.abstract.payment'
|
||||||
|
|
||||||
|
# commented as per Ajeenckya's suggestion, as keeping bank journals in selection, will fulfill the generic way of using payment
|
||||||
|
# journal_id = fields.Many2one('account.journal', string='Payment Journal', required=True,
|
||||||
|
# domain=[('type', '=', 'cash')])
|
||||||
|
|
||||||
|
|
||||||
|
class AccountPayment(models.Model):
|
||||||
|
_inherit = 'account.payment'
|
||||||
|
|
||||||
|
@api.onchange('partner_id', 'amount')
|
||||||
|
def _calculate_balances(self):
|
||||||
|
if(self.state != 'posted'):
|
||||||
|
partner = self.partner_id
|
||||||
|
balance = partner.credit or partner.debit
|
||||||
|
self.balance_before_pay = balance
|
||||||
|
self.total_balance = balance - self.amount
|
||||||
|
|
||||||
|
@api.onchange('invoice_ids')
|
||||||
|
def onchange_partner_id(self):
|
||||||
|
if self.invoice_ids:
|
||||||
|
bill_amount = 0
|
||||||
|
for inv in self.invoice_ids:
|
||||||
|
bill_amount += inv.amount_total
|
||||||
|
self.bill_amount = bill_amount
|
||||||
|
|
||||||
|
@api.onchange('payment_type')
|
||||||
|
def _onchange_payment_type(self):
|
||||||
|
if not self.invoice_ids:
|
||||||
|
# Set default partner type for the payment type
|
||||||
|
if self.payment_type == 'inbound':
|
||||||
|
self.partner_type = 'customer'
|
||||||
|
elif self.payment_type == 'outbound':
|
||||||
|
self.partner_type = 'supplier'
|
||||||
|
else:
|
||||||
|
self.partner_type = False
|
||||||
|
# Set payment method domain
|
||||||
|
res = self._onchange_journal()
|
||||||
|
if not res.get('domain', {}):
|
||||||
|
res['domain'] = {}
|
||||||
|
res['domain']['journal_id'] = self.payment_type == 'inbound' and [('at_least_one_inbound', '=', True)] or self.payment_type == 'outbound' and [('at_least_one_outbound', '=', True)] or []
|
||||||
|
#res['domain']['journal_id'].append(('type', '=', 'cash'))
|
||||||
|
return res
|
||||||
|
|
||||||
|
balance_before_pay = fields.Float(compute=_calculate_balances,
|
||||||
|
string="Balance before pay")
|
||||||
|
total_balance = fields.Float(compute=_calculate_balances,
|
||||||
|
string="Total Balance")
|
||||||
|
invoice_id = fields.Many2one('account.invoice', string='Invoice')
|
||||||
|
bill_amount = fields.Float(string="Bill Amount")
|
BIN
bahmni-addons/bahmni_account/models/account_payment.pyc
Normal file
BIN
bahmni-addons/bahmni_account/models/account_payment.pyc
Normal file
Binary file not shown.
8
bahmni-addons/bahmni_account/models/res_company.py
Normal file
8
bahmni-addons/bahmni_account/models/res_company.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from odoo import models, fields
|
||||||
|
|
||||||
|
|
||||||
|
class ResCompany(models.Model):
|
||||||
|
_inherit = 'res.company'
|
||||||
|
|
||||||
|
round_off_by = fields.Float(string="Round off by")
|
BIN
bahmni-addons/bahmni_account/models/res_company.pyc
Normal file
BIN
bahmni-addons/bahmni_account/models/res_company.pyc
Normal file
Binary file not shown.
8
bahmni-addons/bahmni_account/models/res_partner.py
Normal file
8
bahmni-addons/bahmni_account/models/res_partner.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from odoo import models, fields
|
||||||
|
|
||||||
|
|
||||||
|
class ResPartner(models.Model):
|
||||||
|
_inherit = 'res.partner'
|
||||||
|
|
||||||
|
initials = fields.Char(string="Initials")
|
15
bahmni-addons/bahmni_account/models/rounding_off.py
Normal file
15
bahmni-addons/bahmni_account/models/rounding_off.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from odoo import models, fields, api
|
||||||
|
|
||||||
|
|
||||||
|
class RoundingOff(models.Model):
|
||||||
|
_name = 'rounding.off'
|
||||||
|
|
||||||
|
def round_off_value_to_nearest(self, value):
|
||||||
|
round_off_by = self.env['ir.values'].get_default('sale.config.settings', 'round_off_by')
|
||||||
|
if(round_off_by > 0):
|
||||||
|
half_round_off_by = round_off_by / 2.0
|
||||||
|
remainder = value % round_off_by
|
||||||
|
return -remainder if remainder < half_round_off_by\
|
||||||
|
else round_off_by - remainder
|
||||||
|
return 0
|
BIN
bahmni-addons/bahmni_account/models/rounding_off.pyc
Normal file
BIN
bahmni-addons/bahmni_account/models/rounding_off.pyc
Normal file
Binary file not shown.
3
bahmni-addons/bahmni_account/report/__init__.py
Normal file
3
bahmni-addons/bahmni_account/report/__init__.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import account_count_report
|
||||||
|
import account_report
|
BIN
bahmni-addons/bahmni_account/report/__init__.pyc
Normal file
BIN
bahmni-addons/bahmni_account/report/__init__.pyc
Normal file
Binary file not shown.
35
bahmni-addons/bahmni_account/report/account_count_report.py
Normal file
35
bahmni-addons/bahmni_account/report/account_count_report.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from odoo import models, fields, api
|
||||||
|
from odoo.tools import drop_view_if_exists
|
||||||
|
from odoo.exceptions import Warning
|
||||||
|
|
||||||
|
|
||||||
|
class AccountCountReport(models.Model):
|
||||||
|
_name = 'account.count.report'
|
||||||
|
_auto = False
|
||||||
|
_description = "Count of account heads in sale orders over a period"
|
||||||
|
|
||||||
|
count = fields.Integer(string="Count", readonly=True)
|
||||||
|
date = fields.Date(string="Date", readonly=True)
|
||||||
|
account_id = fields.Many2one('account.account', string="Account")
|
||||||
|
|
||||||
|
@api.model_cr
|
||||||
|
def init(self):
|
||||||
|
drop_view_if_exists(self.env.cr, 'account_count_report')
|
||||||
|
self.env.cr.execute("""
|
||||||
|
create or replace view account_count_report as (
|
||||||
|
select
|
||||||
|
concat(ail.account_id, '_', ai.date_invoice) as id,
|
||||||
|
ai.date_invoice as date,
|
||||||
|
ail.account_id as account_id,
|
||||||
|
count(*) as count
|
||||||
|
from account_invoice ai, account_invoice_line ail
|
||||||
|
where
|
||||||
|
ail.invoice_id = ai.id
|
||||||
|
and ai.type != 'out_refund'
|
||||||
|
group by ail.account_id, ai.date_invoice
|
||||||
|
)""")
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def unlink(self):
|
||||||
|
raise Warning('You cannot delete any record!')
|
BIN
bahmni-addons/bahmni_account/report/account_count_report.pyc
Normal file
BIN
bahmni-addons/bahmni_account/report/account_count_report.pyc
Normal file
Binary file not shown.
71
bahmni-addons/bahmni_account/report/account_count_report.xml
Normal file
71
bahmni-addons/bahmni_account/report/account_count_report.xml
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="view_account_count_report_tree" model="ir.ui.view">
|
||||||
|
<field name="name">account.count.report.tree</field>
|
||||||
|
<field name="model">account.count.report</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<tree string="Accounts Report">
|
||||||
|
<field name="date"/>
|
||||||
|
<field name="account_id"/>
|
||||||
|
<field name="count"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="view_account_count_report_filter" model="ir.ui.view">
|
||||||
|
<field name="name">account.count.report.filter</field>
|
||||||
|
<field name="model">account.count.report</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<search string="Search Accounts Report">
|
||||||
|
<field name="account_id"/>
|
||||||
|
|
||||||
|
<filter string="Last 30 Days" name="filter_month"
|
||||||
|
domain="[
|
||||||
|
['date', '<=', context_today().strftime('%%Y-%%m-%%d')],
|
||||||
|
['date', '>=', (context_today() - datetime.timedelta(30)).strftime('%%Y-%%m-%%d')]
|
||||||
|
]" />
|
||||||
|
|
||||||
|
<filter string="Today" name="filter_today"
|
||||||
|
domain="[('date', '=', context_today().strftime('%%Y-%%m-%%d'))]"/>
|
||||||
|
|
||||||
|
<group string="Group By...">
|
||||||
|
<filter string="Account" name="groupby_account_head"
|
||||||
|
icon="terp-go-month" domain="[]" context="{'group_by':'account_id'}"/>
|
||||||
|
<filter string="Date" name="groupby_date_head"
|
||||||
|
icon="terp-go-month" domain="[]" context="{'group_by':'date'}"/>
|
||||||
|
</group>
|
||||||
|
</search>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_search_account_count_reports" model="ir.actions.act_window">
|
||||||
|
<field name="name">Accounts Count Report</field>
|
||||||
|
<field name="type">ir.actions.act_window</field>
|
||||||
|
<field name="res_model">account.count.report</field>
|
||||||
|
<field name="view_type">form</field>
|
||||||
|
<field name="view_mode">tree</field>
|
||||||
|
<field name="view_id" ref="view_account_count_report_tree"/>
|
||||||
|
<field name="search_view_id" ref="view_account_count_report_filter"/>
|
||||||
|
<field name="context">{}</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem action="action_search_account_count_reports"
|
||||||
|
id="menu_action_search_account_count_reports"
|
||||||
|
parent="account.menu_finance_reports" groups="account.group_account_user" />
|
||||||
|
|
||||||
|
<!--<record id="filter_account_count_report_by_account_id" model="ir.filters">
|
||||||
|
<field name="name">Required Accounts Only</field>
|
||||||
|
<field name="model_id">account.count.report</field>
|
||||||
|
<field name="is_default">1</field>
|
||||||
|
<field name="user_id"></field>
|
||||||
|
<field name="domain">
|
||||||
|
[ '|', '|', '|',
|
||||||
|
['account_id', 'ilike', 'ECG'],
|
||||||
|
['account_id', 'ilike', 'X-Ray'],
|
||||||
|
['account_id', 'ilike', 'MTP'],
|
||||||
|
['account_id', 'ilike', 'Sonography']
|
||||||
|
]
|
||||||
|
</field>
|
||||||
|
</record>-->
|
||||||
|
</odoo>
|
76
bahmni-addons/bahmni_account/report/account_report.py
Normal file
76
bahmni-addons/bahmni_account/report/account_report.py
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from odoo import models, fields, api
|
||||||
|
from odoo.tools import drop_view_if_exists
|
||||||
|
|
||||||
|
|
||||||
|
class AccountReport(models.Model):
|
||||||
|
_name = 'account.report'
|
||||||
|
_description = "Account reports for actual & received amount"
|
||||||
|
_auto = False
|
||||||
|
|
||||||
|
actual_amount = fields.Integer(string="Expenses")
|
||||||
|
amount_received = fields.Integer(string="Collections")
|
||||||
|
date = fields.Date(string="Date")
|
||||||
|
account_id = fields.Many2one('account.account', string="Account Head")
|
||||||
|
|
||||||
|
@api.model_cr
|
||||||
|
def init(self):
|
||||||
|
drop_view_if_exists(self.env.cr, 'account_report')
|
||||||
|
self.env.cr.execute("""
|
||||||
|
create or replace view account_report as (
|
||||||
|
(select id,date,account_id, sum(actual_amount) as actual_amount,sum(amount_received) as amount_received
|
||||||
|
from (
|
||||||
|
SELECT
|
||||||
|
pg_catalog.concat(ail.account_id, '_', ai.date_invoice, '_', ai.type) AS id,ai.id as invoice_id,
|
||||||
|
ai.date_invoice AS date,
|
||||||
|
ail.account_id,
|
||||||
|
CASE
|
||||||
|
WHEN ai.type = 'out_refund' THEN 0
|
||||||
|
ELSE sum(ail.price_subtotal)
|
||||||
|
END AS actual_amount,
|
||||||
|
CASE
|
||||||
|
WHEN ai.type = 'out_refund' THEN sum(
|
||||||
|
(-ail.price_subtotal) * (ai.amount_total / (ai.amount_tax + ai.amount_untaxed)))
|
||||||
|
ELSE sum(ail.price_subtotal * (ai.amount_total / (ai.amount_tax + ai.amount_untaxed)))
|
||||||
|
END AS amount_received
|
||||||
|
FROM account_invoice ai, account_invoice_line ail
|
||||||
|
WHERE ail.invoice_id = ai.id AND (ai.amount_tax + ai.amount_untaxed) <> 0 AND state = 'paid'
|
||||||
|
GROUP BY ail.account_id, ai.date_invoice, ai.type, ai.id
|
||||||
|
UNION
|
||||||
|
SELECT
|
||||||
|
pg_catalog.concat(ail.account_id, '_', ai.date_invoice, '_', ai.type) AS id,ai.id as invoice_id,
|
||||||
|
ai.date_invoice AS date,
|
||||||
|
ail.account_id,
|
||||||
|
CASE
|
||||||
|
WHEN ai.type = 'out_refund' THEN 0
|
||||||
|
ELSE max(ai.amount_total)
|
||||||
|
END AS actual_amount,
|
||||||
|
CASE
|
||||||
|
WHEN ai.type = 'out_refund' THEN max(ai.amount_total) * -1
|
||||||
|
ELSE max(ai.amount_total)
|
||||||
|
END AS amount_received
|
||||||
|
FROM account_invoice ai, account_invoice_line ail
|
||||||
|
WHERE ail.invoice_id = ai.id AND (ai.amount_tax + ai.amount_untaxed) = 0 AND state = 'paid'
|
||||||
|
and ail.account_id not in
|
||||||
|
(select id from account_account where name in ('FINE','Discount','Overcharge'))
|
||||||
|
GROUP BY ail.account_id, ai.date_invoice, ai.type,ai.id
|
||||||
|
UNION
|
||||||
|
SELECT
|
||||||
|
pg_catalog.concat(ail.account_id, '_', ai.date_invoice, '_', ai.type) AS id,ai.id as invoice_id,
|
||||||
|
ai.date_invoice AS date,
|
||||||
|
ail.account_id,
|
||||||
|
CASE
|
||||||
|
WHEN ai.type = 'out_refund' THEN 0
|
||||||
|
ELSE sum(ai.amount_total)
|
||||||
|
END AS actual_amount,
|
||||||
|
CASE
|
||||||
|
WHEN ai.type = 'out_refund' THEN sum(-ai.amount_total)
|
||||||
|
ELSE sum(ai.amount_total)
|
||||||
|
END AS amount_received
|
||||||
|
FROM account_invoice ai, account_invoice_line ail
|
||||||
|
WHERE ail.invoice_id = ai.id AND (ai.amount_tax + ai.amount_untaxed) = 0 AND state = 'paid'
|
||||||
|
and ail.account_id in
|
||||||
|
(select id from account_account where name in ('FINE','Discount','Overcharge'))
|
||||||
|
GROUP BY ail.account_id, ai.date_invoice, ai.type,ai.id
|
||||||
|
) as r group by id,date,account_id)
|
||||||
|
)""")
|
BIN
bahmni-addons/bahmni_account/report/account_report.pyc
Normal file
BIN
bahmni-addons/bahmni_account/report/account_report.pyc
Normal file
Binary file not shown.
59
bahmni-addons/bahmni_account/report/account_report.xml
Normal file
59
bahmni-addons/bahmni_account/report/account_report.xml
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="view_account_report_tree" model="ir.ui.view">
|
||||||
|
<field name="name">account.report.tree</field>
|
||||||
|
<field name="model">account.report</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<tree string="Accounts Report">
|
||||||
|
<field name="date"/>
|
||||||
|
<field name="account_id"/>
|
||||||
|
<field name="amount_received" sum="Total Collections"/>
|
||||||
|
<field name="actual_amount" sum="Total Expense"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="view_account_report_filter" model="ir.ui.view">
|
||||||
|
<field name="name">account.report.filter</field>
|
||||||
|
<field name="model">account.report</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<search string="Search Accounts Report">
|
||||||
|
<field name="account_id"/>
|
||||||
|
|
||||||
|
<filter string="Last 30 Days" name="filter_month"
|
||||||
|
domain="[
|
||||||
|
['date', '<=', context_today().strftime('%%Y-%%m-%%d')],
|
||||||
|
['date', '>=', (context_today() - datetime.timedelta(30)).strftime('%%Y-%%m-%%d')]
|
||||||
|
]" />
|
||||||
|
|
||||||
|
<filter string="Today" name="filter_today"
|
||||||
|
domain="[('date', '=', context_today().strftime('%%Y-%%m-%%d'))]"/>
|
||||||
|
|
||||||
|
<group string="Group By...">
|
||||||
|
<filter string="Account" name="groupby_account_head"
|
||||||
|
icon="terp-go-month" domain="[]" context="{'group_by':'account_id'}"/>
|
||||||
|
<filter string="Date" name="groupby_date_head"
|
||||||
|
icon="terp-go-month" domain="[]" context="{'group_by':'date'}"/>
|
||||||
|
</group>
|
||||||
|
</search>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_search_account_reports" model="ir.actions.act_window">
|
||||||
|
<field name="name">Account Report</field>
|
||||||
|
<field name="type">ir.actions.act_window</field>
|
||||||
|
<field name="res_model">account.report</field>
|
||||||
|
<field name="view_type">form</field>
|
||||||
|
<field name="view_mode">tree</field>
|
||||||
|
<field name="view_id" ref="view_account_report_tree"/>
|
||||||
|
<field name="search_view_id" ref="view_account_report_filter"/>
|
||||||
|
<field name="context">{}</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem action="action_search_account_reports"
|
||||||
|
id="menu_action_search_account_reports"
|
||||||
|
parent="account.menu_finance_reports" groups="account.group_account_user"
|
||||||
|
sequence="3"/>
|
||||||
|
|
||||||
|
</odoo>
|
168
bahmni-addons/bahmni_account/report/report_invoice_inherit.xml
Normal file
168
bahmni-addons/bahmni_account/report/report_invoice_inherit.xml
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<odoo>
|
||||||
|
<template id="account.report_invoice_document">
|
||||||
|
<t t-call="report.external_layout">
|
||||||
|
<t t-set="o" t-value="o.with_context({'lang':o.partner_id.lang})" />
|
||||||
|
<div class="page">
|
||||||
|
<div class="row">
|
||||||
|
<div name="invoice_address" class="col-xs-5 col-xs-offset-7">
|
||||||
|
<address t-field="o.partner_id"
|
||||||
|
t-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": True}' />
|
||||||
|
<span t-if="o.partner_id.vat">TIN: <span t-field="o.partner_id.vat"/></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>
|
||||||
|
<span t-if="o.type == 'out_invoice' and (o.state == 'open' or o.state == 'paid')">Invoice</span>
|
||||||
|
<span t-if="o.type == 'out_invoice' and o.state == 'proforma2'">PRO-FORMA</span>
|
||||||
|
<span t-if="o.type == 'out_invoice' and o.state == 'draft'">Draft Invoice</span>
|
||||||
|
<span t-if="o.type == 'out_invoice' and o.state == 'cancel'">Cancelled Invoice</span>
|
||||||
|
<span t-if="o.type == 'out_refund'">Refund</span>
|
||||||
|
<span t-if="o.type == 'in_refund'">Vendor Refund</span>
|
||||||
|
<span t-if="o.type == 'in_invoice'">Vendor Bill</span>
|
||||||
|
<span t-field="o.number"/>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div class="row mt32 mb32">
|
||||||
|
<div class="col-xs-2" t-if="o.name">
|
||||||
|
<strong>Description:</strong>
|
||||||
|
<p t-field="o.name"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-2" t-if="o.date_invoice">
|
||||||
|
<strong>Invoice Date:</strong>
|
||||||
|
<p t-field="o.date_invoice"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-2" t-if="o.date_due and o.type == 'out_invoice' and (o.state == 'open' or o.state == 'paid')">
|
||||||
|
<strong>Due Date:</strong>
|
||||||
|
<p t-field="o.date_due"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-2" t-if="o.origin">
|
||||||
|
<strong>Source:</strong>
|
||||||
|
<p t-field="o.origin"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-2" t-if="o.partner_id.ref">
|
||||||
|
<strong>Customer Code:</strong>
|
||||||
|
<p t-field="o.partner_id.ref"/>
|
||||||
|
</div>
|
||||||
|
<div name="reference" class="col-xs-2" t-if="o.reference">
|
||||||
|
<strong>Reference:</strong>
|
||||||
|
<p t-field="o.reference"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Is there a discount on at least one line? -->
|
||||||
|
<t t-set="display_discount" t-value="any([l.discount for l in o.invoice_line_ids])"/>
|
||||||
|
|
||||||
|
<table class="table table-condensed">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Description</th>
|
||||||
|
<th class="hidden">Source Document</th>
|
||||||
|
<th class="text-right">Quantity</th>
|
||||||
|
<th class="text-right">Batch No</th>
|
||||||
|
<th class="text-right">Expiry Date</th>
|
||||||
|
<th class="text-right">Unit Price</th>
|
||||||
|
<th t-if="display_discount" class="text-right">Disc.(%)</th>
|
||||||
|
<th class="text-right">Taxes</th>
|
||||||
|
<th class="text-right">Tax Excluded Price</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="invoice_tbody">
|
||||||
|
<tr t-foreach="o.invoice_line_ids" t-as="l">
|
||||||
|
<td><span t-field="l.name"/></td>
|
||||||
|
<td class="hidden"><span t-field="l.origin"/></td>
|
||||||
|
<td class="text-right">
|
||||||
|
<span t-field="l.quantity"/>
|
||||||
|
<span t-field="l.uom_id" groups="product.group_uom"/>
|
||||||
|
</td>
|
||||||
|
<td class="text-right">
|
||||||
|
<span t-field="l.lot_id.name"/>
|
||||||
|
</td>
|
||||||
|
<td class="text-right">
|
||||||
|
<span t-field="l.expiry_date"/>
|
||||||
|
</td>
|
||||||
|
<td class="text-right">
|
||||||
|
<span t-field="l.price_unit"/>
|
||||||
|
</td>
|
||||||
|
<td t-if="display_discount" class="text-right">
|
||||||
|
<span t-field="l.discount"/>
|
||||||
|
</td>
|
||||||
|
<td class="text-right">
|
||||||
|
<span t-esc="', '.join(map(lambda x: (x.description or x.name), l.invoice_line_tax_ids))"/>
|
||||||
|
</td>
|
||||||
|
<td class="text-right">
|
||||||
|
<span t-field="l.price_subtotal"
|
||||||
|
t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-4 pull-right">
|
||||||
|
<table class="table table-condensed">
|
||||||
|
<tr class="border-black">
|
||||||
|
<td><strong>Subtotal</strong></td>
|
||||||
|
<td class="text-right">
|
||||||
|
<span t-field="o.amount_untaxed" t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<t t-foreach="o._get_tax_amount_by_group()" t-as="amount_by_group">
|
||||||
|
<tr>
|
||||||
|
<td><span t-esc="amount_by_group[0] if len(o.tax_line_ids) > 1 else (o.tax_line_ids.tax_id.description or o.tax_line_ids.tax_id.name)"/></td>
|
||||||
|
<td class="text-right">
|
||||||
|
<span t-esc="amount_by_group[2]"/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</t>
|
||||||
|
<tr class="border-black">
|
||||||
|
<td><strong>Total</strong></td>
|
||||||
|
<td class="text-right">
|
||||||
|
<span t-field="o.amount_total" t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- DO NOT REMOVE THIS TABLE. MANDATORY IN SOME COUNTRIES -->
|
||||||
|
<div class="row" t-if="len(o.tax_line_ids) > 0">
|
||||||
|
<div class="col-xs-6">
|
||||||
|
<table class="table table-condensed">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Tax</th>
|
||||||
|
<th class="text-right">Base</th>
|
||||||
|
<th class="text-right">Amount</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr t-foreach="o.tax_line_ids" t-as="t">
|
||||||
|
<td><span t-field="t.tax_id.description"/></td>
|
||||||
|
<td class="text-right">
|
||||||
|
<span t-field="t.base" t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
|
||||||
|
</td>
|
||||||
|
<td class="text-right">
|
||||||
|
<span t-field="t.amount" t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p t-if="o.comment">
|
||||||
|
<strong>Comment:</strong>
|
||||||
|
<span t-field="o.comment"/>
|
||||||
|
</p>
|
||||||
|
<p t-if="o.payment_term_id">
|
||||||
|
<span t-field="o.payment_term_id.note"/>
|
||||||
|
</p>
|
||||||
|
<p t-if="o.fiscal_position_id.note">
|
||||||
|
<strong>Fiscal Position Remark:</strong>
|
||||||
|
<span t-field="o.fiscal_position_id.note"/>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</template>
|
||||||
|
</odoo>
|
@ -0,0 +1,4 @@
|
|||||||
|
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||||
|
access_account_count_report_account_user,Account Count Report Account User,model_account_count_report,account.group_account_user,1,1,1,0
|
||||||
|
access_rounding_off,access_rounding_off,model_rounding_off,,1,1,1,1
|
||||||
|
access_account_report,access_account_report,model_account_report,,1,1,1,1
|
|
BIN
bahmni-addons/bahmni_account/static/description/icon.png
Normal file
BIN
bahmni-addons/bahmni_account/static/description/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
@ -0,0 +1,20 @@
|
|||||||
|
/* css code for sale order form footer */
|
||||||
|
.footer_label{
|
||||||
|
padding-right:0px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer_label > label:after{
|
||||||
|
content: ": "
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.footer_field{
|
||||||
|
padding-right:0px;
|
||||||
|
float: right !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer_text_bold{
|
||||||
|
font-size: 1.3em;
|
||||||
|
padding-right: 80px;
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="inherit_view_account_config_settings" model="ir.ui.view">
|
||||||
|
<field name="name">inherit.view.account.config.settings</field>
|
||||||
|
<field name="model">account.config.settings</field>
|
||||||
|
<field name="inherit_id" ref="account.view_account_config_settings"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//separator[@name='analytic_account']" position="before">
|
||||||
|
<group>
|
||||||
|
<label for="round_off_by"/>
|
||||||
|
<field name="round_off_by" nolabel="1"/>
|
||||||
|
</group>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
186
bahmni-addons/bahmni_account/views/account_invoice_view.xml
Normal file
186
bahmni-addons/bahmni_account/views/account_invoice_view.xml
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="inherit_invoice_form" model="ir.ui.view">
|
||||||
|
<field name="name">inherit.invoice.form</field>
|
||||||
|
<field name="model">account.invoice</field>
|
||||||
|
<field name="inherit_id" ref="account.invoice_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//notebook/page/group" position="replace">
|
||||||
|
<div class="col-xs-5 pull-right text-right" style="padding-right:0px">
|
||||||
|
<div class="col-xs-6 footer_label" style="min-height: 28px;">
|
||||||
|
<label for="amount_untaxed" />
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-6 footer_field" style="min-height: 28px;">
|
||||||
|
<field name="amount_untaxed"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-6 footer_label" style="min-height: 28px;">
|
||||||
|
<label for="amount_tax"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-6 footer_field" style="min-height: 28px;">
|
||||||
|
<field name="amount_tax"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-6 footer_label" style="min-height: 28px;">
|
||||||
|
<label for="discount_type"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-6 footer_field" style="min-height: 28px;">
|
||||||
|
<field name="discount_type" required="1"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-6 footer_label" style="min-height: 28px;">
|
||||||
|
<label for="round_off_amount"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-6 footer_field" style="min-height: 28px;">
|
||||||
|
<field name="round_off_amount"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-6 footer_label" style="min-height: 28px;"
|
||||||
|
attrs="{'invisible': [('discount_type', '!=', 'percentage')]}">
|
||||||
|
<label for="discount_percentage" />
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-6 footer_field" style="white-space:nowrap;padding-right:15px"
|
||||||
|
attrs="{'invisible': [('discount_type', '!=', 'percentage')]}" >
|
||||||
|
<field name="discount_percentage" /> <b style="padding-left:2px">%</b>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-6 footer_label" style="min-height:33px;"
|
||||||
|
attrs="{'invisible': [('discount_type', '=', 'none')]}">
|
||||||
|
<label for="discount" />
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-6 footer_field" style="min-height:33px;"
|
||||||
|
attrs="{'invisible': [('discount_type', '=', 'none')]}">
|
||||||
|
<field name="discount" style="width:100% !important" context="{'readonly_by_pass': True}"
|
||||||
|
attrs="{'readonly': [('discount_type', '=', 'percentage')]}"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-6 footer_label"
|
||||||
|
attrs="{'invisible': [('discount_type', '=', 'none')]}">
|
||||||
|
<label for="disc_acc_id" name="remove_display_prop"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-6 footer_field" attrs="{'invisible': [('discount_type', '=', 'none')]}">
|
||||||
|
<field name="disc_acc_id"
|
||||||
|
attrs="{'required': [('discount_type', 'in', ('fixed', 'percentage'))]}"/>
|
||||||
|
</div>
|
||||||
|
<group style="width: 100%; border-top: 1px solid #cacaca;
|
||||||
|
font-weight: bold; white-space:nowrap;">
|
||||||
|
<div class="col-xs-6 oe_right footer_label">
|
||||||
|
<label for="amount_total" style="font-weight:bold"/>
|
||||||
|
<button name="button_dummy" states="draft,sent" string="(update)"
|
||||||
|
type="object" class="oe_edit_only oe_link"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-6 footer_field footer_text_bold" >
|
||||||
|
<field name="amount_total" />
|
||||||
|
</div>
|
||||||
|
</group>
|
||||||
|
<group style="width: 100%; border-top: 1px solid #cacaca;
|
||||||
|
font-weight: bold; white-space:nowrap;">
|
||||||
|
<div class="col-xs-6 oe_right footer_label">
|
||||||
|
<label for="residual" style="font-weight:bold"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-6 footer_field footer_text_bold" >
|
||||||
|
<field name="residual" />
|
||||||
|
</div>
|
||||||
|
</group>
|
||||||
|
</div>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="account.action_invoice_tree1" model="ir.actions.act_window">
|
||||||
|
<field name="name">Customer Invoices</field>
|
||||||
|
<field name="res_model">account.invoice</field>
|
||||||
|
<field name="view_type">form</field>
|
||||||
|
<field name="view_mode">tree,kanban,form,calendar,pivot,graph</field>
|
||||||
|
<field eval="False" name="view_id"/>
|
||||||
|
<field name="domain">[('type','in',('out_invoice', 'out_refund'))]</field>
|
||||||
|
<field name="context">{'type':'out_invoice', 'journal_type': 'sale',
|
||||||
|
'readonly_by_pass': True}
|
||||||
|
</field>
|
||||||
|
<field name="search_view_id" ref="account.view_account_invoice_filter"/>
|
||||||
|
<field name="help" type="html">
|
||||||
|
<p class="oe_view_nocontent_create">
|
||||||
|
Click to create a customer invoice.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Odoo's electronic invoicing allows to ease and fasten the
|
||||||
|
collection of customer payments. Your customer receives the
|
||||||
|
invoice by email and he can pay online and/or import it
|
||||||
|
in his own system.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The discussions with your customer are automatically displayed at
|
||||||
|
the bottom of each invoice.
|
||||||
|
</p>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!--Supplier Form-->
|
||||||
|
<record id="inherit_invoice_supplier_form" model="ir.ui.view">
|
||||||
|
<field name="name">inherit.invoice.supplier.form</field>
|
||||||
|
<field name="model">account.invoice</field>
|
||||||
|
<field name="inherit_id" ref="account.invoice_supplier_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//notebook/page/group" position="replace">
|
||||||
|
<div class="col-xs-5 pull-right text-right" style="padding-right:0px">
|
||||||
|
<div class="col-xs-6 footer_label" style="min-height: 28px;">
|
||||||
|
<label for="amount_untaxed" />
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-6 footer_field" style="min-height: 28px;">
|
||||||
|
<field name="amount_untaxed"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-6 footer_label" style="min-height: 28px;">
|
||||||
|
<label for="amount_tax"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-6 footer_field" style="min-height: 28px;">
|
||||||
|
<field name="amount_tax"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-6 footer_label" style="min-height: 28px;">
|
||||||
|
<label for="discount_type"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-6 footer_field" style="min-height: 28px;">
|
||||||
|
<field name="discount_type" required="1"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-6 footer_label" style="min-height: 28px;">
|
||||||
|
<label for="round_off_amount"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-6 footer_field" style="min-height: 28px;">
|
||||||
|
<field name="round_off_amount"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-6 footer_label" style="min-height: 28px;"
|
||||||
|
attrs="{'invisible': [('discount_type', '!=', 'percentage')]}">
|
||||||
|
<label for="discount_percentage" />
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-6 footer_field" style="white-space:nowrap;padding-right:15px"
|
||||||
|
attrs="{'invisible': [('discount_type', '!=', 'percentage')]}" >
|
||||||
|
<field name="discount_percentage" /> <b style="padding-left:2px">%</b>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-6 footer_label" style="min-height:33px;"
|
||||||
|
attrs="{'invisible': [('discount_type', '=', 'none')]}">
|
||||||
|
<label for="discount" />
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-6 footer_field" style="min-height:33px;"
|
||||||
|
attrs="{'invisible': [('discount_type', '=', 'none')]}">
|
||||||
|
<field name="discount" style="width:100% !important" context="{'readonly_by_pass': True}"
|
||||||
|
attrs="{'readonly': [('discount_type', '=', 'percentage')]}"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-6 footer_label"
|
||||||
|
attrs="{'invisible': [('discount_type', '=', 'none')]}">
|
||||||
|
<label for="disc_acc_id" name="remove_display_prop"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-6 footer_field" attrs="{'invisible': [('discount_type', '=', 'none')]}">
|
||||||
|
<field name="disc_acc_id"
|
||||||
|
attrs="{'required': [('discount_type', 'in', ('fixed', 'percentage'))]}"/>
|
||||||
|
</div>
|
||||||
|
<group style="width: 100%; border-top: 1px solid #cacaca;
|
||||||
|
font-weight: bold; white-space:nowrap;">
|
||||||
|
<div class="col-xs-6 oe_right footer_label">
|
||||||
|
<label for="amount_total" />
|
||||||
|
<button name="button_dummy" states="draft,sent" string="(update)"
|
||||||
|
type="object" class="oe_edit_only oe_link"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-6 footer_field footer_text_bold" >
|
||||||
|
<field name="amount_total" />
|
||||||
|
</div>
|
||||||
|
</group>
|
||||||
|
</div>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
44
bahmni-addons/bahmni_account/views/account_payment.xml
Normal file
44
bahmni-addons/bahmni_account/views/account_payment.xml
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="view_account_payment_form_inherit" model="ir.ui.view">
|
||||||
|
<field name="name">view.account.payment.form.inherit</field>
|
||||||
|
<field name="model">account.payment</field>
|
||||||
|
<field name="inherit_id" ref="account.view_account_payment_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//field[@name='journal_id']" position="before">
|
||||||
|
<field name="balance_before_pay" string="Amount Due"/>
|
||||||
|
<field name="bill_amount"/>
|
||||||
|
<field name="total_balance"/>
|
||||||
|
<field name="invoice_id"/>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//field[@name='journal_id']" position="replace">
|
||||||
|
<field name="journal_id" widget="selection"
|
||||||
|
attrs="{'readonly': [('state', '!=', 'draft')]}"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="view_register_payment_inherit" model="ir.ui.view">
|
||||||
|
<field name="name">view.register.payment.inherit</field>
|
||||||
|
<field name="model">account.payment</field>
|
||||||
|
<field name="inherit_id" ref="account.view_account_payment_invoice_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//label[@for='amount']" position="before">
|
||||||
|
<field name="balance_before_pay" readonly="1"/>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//div[@name='amount_div']" position="after">
|
||||||
|
<field name="bill_amount" readonly="1"/>
|
||||||
|
<field name="total_balance" readonly="1"/>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//field[@name='journal_id']" position="replace">
|
||||||
|
<field name="journal_id" widget="selection"/>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//button[@name='post']" position="replace">
|
||||||
|
<button string="Validate" name="post" type="object" class="btn-primary"
|
||||||
|
context="{'readonly_by_pass': True}"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
13
bahmni-addons/bahmni_account/views/bahmni_account.xml
Normal file
13
bahmni-addons/bahmni_account/views/bahmni_account.xml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
<template id="assets_backend" name="bahmni_account assets" inherit_id="web.assets_backend">
|
||||||
|
<xpath expr="." position="inside">
|
||||||
|
<link rel="stylesheet" href="/bahmni_account/static/src/css/account_invoice_footer.css"/>
|
||||||
|
<!-- <script type="text/javascript" src="/bahmni_account/static/src/js/sale_order_footer.js"></script-->
|
||||||
|
|
||||||
|
<!-- <script type="text/javascript" src="/account/static/src/js/tour_bank_statement_reconciliation.js"></script> -->
|
||||||
|
</xpath>
|
||||||
|
</template>
|
||||||
|
</data>
|
||||||
|
</odoo>
|
3
bahmni-addons/bahmni_atom_feed/__init__.py
Normal file
3
bahmni-addons/bahmni_atom_feed/__init__.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import models
|
||||||
|
import wizard
|
30
bahmni-addons/bahmni_atom_feed/__manifest__.py
Normal file
30
bahmni-addons/bahmni_atom_feed/__manifest__.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
{
|
||||||
|
'name': 'Bahmni Atom Feed',
|
||||||
|
'version': '1.0',
|
||||||
|
'summary': 'Module to sync Bahmni with Odoo',
|
||||||
|
'sequence': 1,
|
||||||
|
'description': """
|
||||||
|
Bahmni Web Extension
|
||||||
|
====================
|
||||||
|
""",
|
||||||
|
'category': 'Web',
|
||||||
|
'website': '',
|
||||||
|
'images': [],
|
||||||
|
'depends': ['web', 'bahmni_product', 'bahmni_sale'],
|
||||||
|
'data': ['security/ir.model.access.csv',
|
||||||
|
'data/mrs_person_attributes_data.xml',
|
||||||
|
'views/event_records_view.xml',
|
||||||
|
'views/res_company.xml',
|
||||||
|
'wizard/stock_location_product_dhis2.xml',
|
||||||
|
'views/order_type_view.xml',
|
||||||
|
'views/syncable_units_mapping_view.xml',
|
||||||
|
'views/order_type_shop_map_view.xml',
|
||||||
|
'views/res_users_view.xml',
|
||||||
|
],
|
||||||
|
'demo': [],
|
||||||
|
'qweb': [],
|
||||||
|
'installable': True,
|
||||||
|
'application': True,
|
||||||
|
'auto_install': False,
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data noupdate="1">
|
||||||
|
<record id="openmrs_patient_attributes" model="ir.config_parameter">
|
||||||
|
<field name="key">openmrs_patient_attributes</field>
|
||||||
|
<field name="value">,</field>
|
||||||
|
</record>
|
||||||
|
</data>
|
||||||
|
</odoo>
|
13
bahmni-addons/bahmni_atom_feed/models/__init__.py
Normal file
13
bahmni-addons/bahmni_atom_feed/models/__init__.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import atom_event_worker
|
||||||
|
import event_records
|
||||||
|
import reference_data_service
|
||||||
|
import drug_data_service
|
||||||
|
import product_uom_service
|
||||||
|
import res_company
|
||||||
|
import res_users
|
||||||
|
import order_type
|
||||||
|
import syncable_units_mapping
|
||||||
|
import atom_feed_marker
|
||||||
|
import order_save_service
|
||||||
|
import order_type_shop_map
|
BIN
bahmni-addons/bahmni_atom_feed/models/__init__.pyc
Normal file
BIN
bahmni-addons/bahmni_atom_feed/models/__init__.pyc
Normal file
Binary file not shown.
208
bahmni-addons/bahmni_atom_feed/models/atom_event_worker.py
Normal file
208
bahmni-addons/bahmni_atom_feed/models/atom_event_worker.py
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from odoo import models, fields, api
|
||||||
|
import logging
|
||||||
|
import json
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
STATE_CODE_PREFIX = 'UNKNOWN-'
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class AtomEventWorker(models.Model):
|
||||||
|
_name = 'atom.event.worker'
|
||||||
|
_auto = False
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def process_event(self, vals):
|
||||||
|
'''Method getting triggered from Bahmni side'''
|
||||||
|
_logger.info("vals")
|
||||||
|
_logger.info(vals)
|
||||||
|
category = vals.get("category")
|
||||||
|
# patient_ref = vals.get("ref")
|
||||||
|
try:
|
||||||
|
if category == "create.customer":
|
||||||
|
self._create_or_update_customer(vals)
|
||||||
|
elif category == "create.drug":
|
||||||
|
self.env['drug.data.service'].create_or_update_drug(vals)
|
||||||
|
elif category == "create.sale.order":
|
||||||
|
self.env['order.save.service'].create_orders(vals)
|
||||||
|
elif category == 'create.drug.category':
|
||||||
|
self.env['drug.data.service'].create_or_update_drug_category(vals)
|
||||||
|
elif category == 'create.drug.uom':
|
||||||
|
self.env['product.uom.service'].create_or_update_product_uom(vals)
|
||||||
|
elif category == 'create.drug.uom.category':
|
||||||
|
self.env['product.uom.service'].create_or_update_product_uom_category(vals)
|
||||||
|
elif category == "create.radiology.test":
|
||||||
|
self.env['reference.data.service'].create_or_update_ref_data(vals, 'Radiology')
|
||||||
|
elif category == "create.lab.test":
|
||||||
|
self.env['reference.data.service'].create_or_update_ref_data(vals, 'Test')
|
||||||
|
elif category == "create.lab.panel":
|
||||||
|
self.env['reference.data.service'].create_or_update_ref_data(vals, 'Panel')
|
||||||
|
elif category == "create.service.saleable":
|
||||||
|
self.env['reference.data.service'].create_or_update_ref_data(vals, 'Others')
|
||||||
|
|
||||||
|
return {'success': True}
|
||||||
|
except Exception as err:
|
||||||
|
_logger.info("\n Processing event threw error: %s", err)
|
||||||
|
raise
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _update_marker(self, feed_uri_for_last_read_entry, last_read_entry_id, marker_ids):
|
||||||
|
for marker_id in marker_ids:
|
||||||
|
marker = self.env['atom.feed.marker']
|
||||||
|
marker._update_marker(marker_id,last_read_entry_id, feed_uri_for_last_read_entry)
|
||||||
|
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _create_marker(self, feed_uri_for_last_read_entry, last_read_entry_id, feed_uri):
|
||||||
|
marker = {'feed_uri': feed_uri, 'last_read_entry_id': last_read_entry_id,
|
||||||
|
'feed_uri_for_last_read_entry': feed_uri_for_last_read_entry}
|
||||||
|
self.env['atom.feed.marker'].create(marker)
|
||||||
|
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _create_or_update_marker(self, vals):
|
||||||
|
'''Method to Create or Update entries for markers table for the event taking place'''
|
||||||
|
is_failed_event = vals.get('is_failed_event',False)
|
||||||
|
if is_failed_event:
|
||||||
|
return
|
||||||
|
|
||||||
|
last_read_entry_id = vals.get('last_read_entry_id')
|
||||||
|
feed_uri_for_last_read_entry = vals.get('feed_uri_for_last_read_entry')
|
||||||
|
feed_uri = vals.get('feed_uri')
|
||||||
|
|
||||||
|
# Rohan/Mujir - do not update markers for failed events (failed events have empty 'feed_uri_for_last_read_entry')
|
||||||
|
if not feed_uri_for_last_read_entry or not feed_uri or "$param" in feed_uri_for_last_read_entry or "$param" in feed_uri:
|
||||||
|
return
|
||||||
|
|
||||||
|
marker = self.env['atom.feed.marker'].search([('feed_uri', '=', feed_uri)],
|
||||||
|
limit=1)
|
||||||
|
|
||||||
|
if marker:
|
||||||
|
self._update_marker(feed_uri_for_last_read_entry, last_read_entry_id, marker)
|
||||||
|
else:
|
||||||
|
self._create_marker(feed_uri_for_last_read_entry, last_read_entry_id, feed_uri)
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _create_or_update_customer(self, vals):
|
||||||
|
patient_ref = vals.get("ref")
|
||||||
|
customer_vals = self._get_customer_vals(vals)
|
||||||
|
# removing null values, as while updating null values in write method will remove old values
|
||||||
|
|
||||||
|
for rec in customer_vals.keys():
|
||||||
|
if not customer_vals[rec]:
|
||||||
|
del customer_vals[rec]
|
||||||
|
existing_customer = self.env['res.partner'].search([('ref', '=', patient_ref)])
|
||||||
|
if existing_customer:
|
||||||
|
existing_customer.write(customer_vals)
|
||||||
|
self._create_or_update_person_attributes(existing_customer.id,vals)
|
||||||
|
else:
|
||||||
|
customer = self.env['res.partner'].create(customer_vals)
|
||||||
|
self._create_or_update_person_attributes(customer.id,vals)
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _get_address_details(self, address):
|
||||||
|
res = {}
|
||||||
|
if address.get('address1'):
|
||||||
|
res.update({'street': address['address1']})
|
||||||
|
if address.get('address2'):
|
||||||
|
res.update({'street2': address['address2']})
|
||||||
|
|
||||||
|
auto_create_customer_address_levels = self.env.ref('bahmni_sale.auto_create_customer_address_levels').value
|
||||||
|
country = self._find_country(address)
|
||||||
|
state = None
|
||||||
|
district = None
|
||||||
|
if address.get("stateProvince") and country:
|
||||||
|
state = self._find_or_create_state(country, address['stateProvince'], auto_create_customer_address_levels)
|
||||||
|
if state:
|
||||||
|
res.update({'state_id': state.id})
|
||||||
|
|
||||||
|
if address.get('countyDistrict') and state:
|
||||||
|
district = self._find_or_create_district(country, state, address.get('countyDistrict'), auto_create_customer_address_levels)
|
||||||
|
if district:
|
||||||
|
res.update({'district_id': district.id})
|
||||||
|
|
||||||
|
# for now, from bahmni side, Taluka is sent as address3
|
||||||
|
if address.get('address3') and district:
|
||||||
|
# =ilike operator will ignore the case of letters while comparing
|
||||||
|
tehsil = self._find_or_create_level3(state, district, address['address3'], auto_create_customer_address_levels)
|
||||||
|
if tehsil:
|
||||||
|
res.update({'tehsil_id': tehsil.id})
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _find_country(self, address):
|
||||||
|
# TODO: from bahmni side, no country is getting passed, so using company's country id
|
||||||
|
# once from bahmni side country name is passed properly, replace with that key
|
||||||
|
return self.env.user.company_id.country_id
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _find_or_create_level3(self, state, district, level_name, auto_create_customer_address_levels):
|
||||||
|
levels = self.env['district.tehsil'].search([('name', '=ilike', level_name),
|
||||||
|
('district_id', '=', district.id if district else False)])
|
||||||
|
if not levels and auto_create_customer_address_levels == '1':
|
||||||
|
# TODO, check if configuration enabled to create level3 if not present
|
||||||
|
level = self.env['district.tehsil'].create({'name': level_name,
|
||||||
|
'district_id': district.id if district else False,
|
||||||
|
'state_id': state.id if state else False})
|
||||||
|
else:
|
||||||
|
level = levels[0]
|
||||||
|
return level
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _find_or_create_district(self, country, state, district_county_name, auto_create_customer_address_levels):
|
||||||
|
districts = self.env['state.district'].search([('name', '=ilike', district_county_name),
|
||||||
|
('state_id', '=', state.id if state else None)])
|
||||||
|
if not districts and auto_create_customer_address_levels == '1':
|
||||||
|
# TODO, check if configuration enabled to create state if not present
|
||||||
|
district = self.env['state.district'].create({'name': district_county_name,
|
||||||
|
'state_id': state.id if state else None,
|
||||||
|
'country_id': country.id})
|
||||||
|
else:
|
||||||
|
district = districts[0]
|
||||||
|
return district
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _find_or_create_state(self, country, state_province_name, auto_create_customer_address_levels):
|
||||||
|
states = self.env['res.country.state'].search([('name', '=ilike', state_province_name),
|
||||||
|
('country_id', '=', country.id)])
|
||||||
|
if not states and auto_create_customer_address_levels == '1':
|
||||||
|
# TODO, check if configuration enabled to create state if not present
|
||||||
|
state_code = STATE_CODE_PREFIX + str(uuid.uuid4())
|
||||||
|
state = self.env['res.country.state'].create({'name': state_province_name,
|
||||||
|
'code': state_code,
|
||||||
|
'country_id': country.id})
|
||||||
|
else:
|
||||||
|
state = states[0]
|
||||||
|
return state
|
||||||
|
|
||||||
|
def _get_customer_vals(self, vals):
|
||||||
|
res = {}
|
||||||
|
res.update({'ref': vals.get('ref'),
|
||||||
|
'name': vals.get('name'),
|
||||||
|
'local_name': vals.get('local_name'),
|
||||||
|
'uuid': vals.get('uuid')})
|
||||||
|
address_data = vals.get('preferredAddress')
|
||||||
|
# get validated address details
|
||||||
|
address_details = self._get_address_details(json.loads(address_data))
|
||||||
|
# update address details
|
||||||
|
res.update(address_details)
|
||||||
|
# update other details : for now there is only scope of updating contact.
|
||||||
|
if vals.get('primaryContact'):
|
||||||
|
res.update({'phone': vals['primaryContact']})
|
||||||
|
return res
|
||||||
|
|
||||||
|
def _create_or_update_person_attributes(self, cust_id, vals):#TODO whole method
|
||||||
|
attributes = json.loads(vals.get("attributes", "{}"))
|
||||||
|
openmrs_patient_attributes = str(self.env.ref('bahmni_atom_feed.openmrs_patient_attributes').value)
|
||||||
|
openmrs_attributes_list = filter(lambda s: len(s) > 0, map(str.strip, openmrs_patient_attributes.split(',')))
|
||||||
|
_logger.info("\n List of Patient Attributes to Sync = %s", openmrs_attributes_list)
|
||||||
|
for key in attributes:
|
||||||
|
if key in openmrs_attributes_list:
|
||||||
|
column_dict = {'partner_id': cust_id}
|
||||||
|
existing_attribute = self.env['res.partner.attributes'].search([('partner_id', '=', cust_id),('name', '=', key)])
|
||||||
|
if any(existing_attribute):
|
||||||
|
existing_attribute.unlink()
|
||||||
|
column_dict.update({"name": key, "value" : attributes[key]})
|
||||||
|
self.env['res.partner.attributes'].create(column_dict)
|
BIN
bahmni-addons/bahmni_atom_feed/models/atom_event_worker.pyc
Normal file
BIN
bahmni-addons/bahmni_atom_feed/models/atom_event_worker.pyc
Normal file
Binary file not shown.
19
bahmni-addons/bahmni_atom_feed/models/atom_feed_marker.py
Normal file
19
bahmni-addons/bahmni_atom_feed/models/atom_feed_marker.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import logging
|
||||||
|
from odoo import models, fields, api
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class AtomFeedMarker(models.Model):
|
||||||
|
_name = 'atom.feed.marker'
|
||||||
|
_table = 'markers'
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _update_marker(self, marker_id,last_read_entry_id,feed_uri_for_last_read_entry):
|
||||||
|
_logger.info("\n\n***marker_id=%s",marker_id)
|
||||||
|
marker = self.browse(marker_id.id)
|
||||||
|
marker.write({'last_read_entry_id': last_read_entry_id,'feed_uri_for_last_read_entry': feed_uri_for_last_read_entry,})
|
||||||
|
_logger.info("\n\n***marker=%s",marker)
|
||||||
|
feed_uri = fields.Char(string="uuid",size=250,translate=True,required=True)
|
||||||
|
last_read_entry_id = fields.Char(string="Title", size=250, translate=True, required=True)
|
||||||
|
feed_uri_for_last_read_entry = fields.Char(string="Category", size=100, translate=True, required=True)
|
BIN
bahmni-addons/bahmni_atom_feed/models/atom_feed_marker.pyc
Normal file
BIN
bahmni-addons/bahmni_atom_feed/models/atom_feed_marker.pyc
Normal file
Binary file not shown.
86
bahmni-addons/bahmni_atom_feed/models/drug_data_service.py
Normal file
86
bahmni-addons/bahmni_atom_feed/models/drug_data_service.py
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from odoo import models, fields, api
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class DrugDataService(models.Model):
|
||||||
|
_name = 'drug.data.service'
|
||||||
|
_auto = False
|
||||||
|
_description = 'Intermediate Class for creating products, through api call'
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def create_or_update_drug_category(self, vals):
|
||||||
|
'''Method to create or update the child product category, under drug category'''
|
||||||
|
drug_categ = json.loads(vals.get("drug_category"))
|
||||||
|
exist_categ = self.env["product.category"].search([('uuid', '=', drug_categ.get("id"))])
|
||||||
|
parent_categ = self.env["product.category"].search([('name', '=', "Drug")], limit=1)
|
||||||
|
updated_categ = self._fill_drug_category(drug_categ, parent_categ.id)
|
||||||
|
if exist_categ:
|
||||||
|
_logger.info("\nupdated : drug_category :\n")
|
||||||
|
_logger.info(updated_categ)
|
||||||
|
_logger.info(exist_categ.id)
|
||||||
|
return exist_categ.write(updated_categ)
|
||||||
|
_logger.info("\ninserted : drug_category :\n")
|
||||||
|
_logger.info(updated_categ)
|
||||||
|
return self.env['product.category'].create(updated_categ)
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _fill_drug_category(self, drug_categ_from_feed, parent_id=None):
|
||||||
|
'''Method to return values for product category record creation/updation'''
|
||||||
|
drug_categ = {}
|
||||||
|
drug_categ["name"] = drug_categ_from_feed.get("name")
|
||||||
|
drug_categ["uuid"] = drug_categ_from_feed.get("id")
|
||||||
|
if parent_id is not None:
|
||||||
|
drug_categ["parent_id"] = parent_id
|
||||||
|
|
||||||
|
_logger.info("drug categ in fill")
|
||||||
|
_logger.info(drug_categ)
|
||||||
|
return drug_categ
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def create_or_update_drug(self, vals):
|
||||||
|
'''Method for creating/updating a new product under drug category'''
|
||||||
|
products = self.env['product.product'].search([('uuid', '=', vals.get("uuid"))])
|
||||||
|
updated_drug = self._fill_drug_object(vals, products.ids)
|
||||||
|
if products:
|
||||||
|
product = self.env['product.product'].browse(products.ids[0:1])
|
||||||
|
product.write(updated_drug)
|
||||||
|
else:
|
||||||
|
self.env['product.product'].create(updated_drug)
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _fill_drug_object(self, drug_from_feed, drug_ids_from_db):
|
||||||
|
'''Method which returns the values for creation/updation of product under drug category'''
|
||||||
|
drug = {}
|
||||||
|
category_name = drug_from_feed.get("dosageForm")
|
||||||
|
category = self.env["product.category"].search([('name', '=', category_name)])
|
||||||
|
if category.read([]):
|
||||||
|
category_from_db = category.read([])[0]
|
||||||
|
categ_id = category_from_db and category_from_db.get('id') or self._create_in_drug_category(category_name)
|
||||||
|
else:
|
||||||
|
categ_id = self.env["product.category"].create({'name':category_name}).id
|
||||||
|
|
||||||
|
list_price = drug_ids_from_db and self.env['product.product'].browse(drug_ids_from_db[0]).list_price or 0.0
|
||||||
|
drug["uuid"] = drug_from_feed.get("uuid")
|
||||||
|
drug["name"] = drug_from_feed.get("name")
|
||||||
|
drug["default_code"] = drug_from_feed.get("shortName")
|
||||||
|
drug["drug"] = drug_from_feed.get("genericName")
|
||||||
|
drug["categ_id"] = categ_id
|
||||||
|
drug["type"] = "product"
|
||||||
|
drug["list_price"] = list_price
|
||||||
|
drug["sale_ok"] = 1
|
||||||
|
drug["purchase_ok"] = 1
|
||||||
|
return drug
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _create_in_drug_category(self, categ_name):
|
||||||
|
'''Method to create a new category, while creating a product, if category does not exists'''
|
||||||
|
parent_categ = self.env["product.category"].search([('name', '=', "Drug")])
|
||||||
|
category = {'name': categ_name}
|
||||||
|
if(parent_categ):
|
||||||
|
category['parent_id'] = parent_categ.id
|
||||||
|
return self.env['product.category'].create(category).id
|
||||||
|
|
BIN
bahmni-addons/bahmni_atom_feed/models/drug_data_service.pyc
Normal file
BIN
bahmni-addons/bahmni_atom_feed/models/drug_data_service.pyc
Normal file
Binary file not shown.
16
bahmni-addons/bahmni_atom_feed/models/event_records.py
Normal file
16
bahmni-addons/bahmni_atom_feed/models/event_records.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from odoo import models, fields, api
|
||||||
|
|
||||||
|
|
||||||
|
class EventRecords(models.Model):
|
||||||
|
_name = 'event.records'
|
||||||
|
_table = 'event_records'
|
||||||
|
_description = 'Class for publishing events for changes done in odoo '\
|
||||||
|
'and needs to be synced with bahmni'
|
||||||
|
|
||||||
|
uuid = fields.Char(string="UUID", translate=True, required=True)
|
||||||
|
title = fields.Char(string="Title", translate=True, required=True)
|
||||||
|
category = fields.Char(string="Category", translate=True, required=True)
|
||||||
|
timestamp = fields.Datetime(string="Timestamp", required=True)
|
||||||
|
uri = fields.Char(string="URI", translate=True)
|
||||||
|
object = fields.Text(string="SerializedContents", required=True)
|
BIN
bahmni-addons/bahmni_atom_feed/models/event_records.pyc
Normal file
BIN
bahmni-addons/bahmni_atom_feed/models/event_records.pyc
Normal file
Binary file not shown.
@ -0,0 +1,43 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from odoo import models, fields, api
|
||||||
|
|
||||||
|
|
||||||
|
class OrderType(models.Model):
|
||||||
|
_name = 'order.type'
|
||||||
|
|
||||||
|
name = fields.Char(string='Name')
|
||||||
|
|
||||||
|
_sql_constraints = [('unique_name', 'unique(name)',
|
||||||
|
'Order type with this name already exists!')]
|
||||||
|
|
||||||
|
''' @api.model
|
||||||
|
def create(self, vals):
|
||||||
|
if vals.get('name'):
|
||||||
|
name = vals['name'].capitalize()
|
||||||
|
vals.update({'name': name})
|
||||||
|
return super(OrderType, self).create(vals)
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def write(self, vals):
|
||||||
|
if vals.get('name'):
|
||||||
|
name = vals['name'].capitalize()
|
||||||
|
vals.update({'name': name})
|
||||||
|
return super(OrderType, self).write(vals)
|
||||||
|
'''
|
||||||
|
|
||||||
|
class OrderPickingTypeMapping(models.Model):
|
||||||
|
_name = 'order.picking.type.mapping'
|
||||||
|
|
||||||
|
order_type_id = fields.Many2one('order.type', string="Order Type")
|
||||||
|
picking_type_id = fields.Many2one('stock.picking.type', string="Picking Type")
|
||||||
|
|
||||||
|
_sql_constraints = [('uniq_order_picking_type', 'unique(order_type_id, picking_type_id)',
|
||||||
|
'Order type and Picking type combination already exists!')]
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def name_get(self):
|
||||||
|
res = []
|
||||||
|
for rec in self:
|
||||||
|
res.append((rec.id, rec.order_type_id.name + ' - ' + rec.picking_type_id.name_get()[0][1]))
|
||||||
|
return res
|
||||||
|
|
556
bahmni-addons/bahmni_atom_feed/models/order_save_service.py
Normal file
556
bahmni-addons/bahmni_atom_feed/models/order_save_service.py
Normal file
@ -0,0 +1,556 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from datetime import datetime
|
||||||
|
import json
|
||||||
|
from itertools import groupby
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from odoo import fields, models, api
|
||||||
|
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT as DTF
|
||||||
|
from odoo.exceptions import Warning
|
||||||
|
|
||||||
|
ORDER_DISPENSED_FALSE = 'false' # type: str
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class OrderSaveService(models.Model):
|
||||||
|
_name = 'order.save.service'
|
||||||
|
_auto = False
|
||||||
|
|
||||||
|
def _get_openerp_orders(self, vals):
|
||||||
|
if not vals.get("orders", None):
|
||||||
|
return None
|
||||||
|
orders_string = vals.get("orders")
|
||||||
|
order_group = json.loads(orders_string)
|
||||||
|
return order_group.get('openERPOrders', None)
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _get_warehouse_id(self, location, order_type_ref):
|
||||||
|
_logger.info("\n identifying warehouse for warehouse %s, location %s", order_type_ref, location)
|
||||||
|
if location:
|
||||||
|
operation_types = self.env['stock.picking.type'].search([('default_location_src_id', '=', location.id)])
|
||||||
|
if operation_types:
|
||||||
|
mapping = self.env['order.picking.type.mapping'].search([('order_type_id', '=', order_type_ref.id),
|
||||||
|
('picking_type_id', 'in', operation_types.ids)],
|
||||||
|
limit=1)
|
||||||
|
if mapping:
|
||||||
|
return mapping.picking_type_id.warehouse_id
|
||||||
|
else:
|
||||||
|
return operation_types[0].warehouse_id
|
||||||
|
|
||||||
|
else:
|
||||||
|
# either location should exist as stock location of a warehouse.
|
||||||
|
warehouse = self.env['stock.warehouse'].search([('lot_stock_id', '=', location.id)])
|
||||||
|
if warehouse:
|
||||||
|
return warehouse.id
|
||||||
|
else:
|
||||||
|
_logger.warning("Location is neither mapped to warehouse nor to any Operation type, "
|
||||||
|
"hence sale order creation failed!")
|
||||||
|
# TODO: the above message seems to indicate that we should fail sale order creation,
|
||||||
|
# but create_orders() seem to behave otherwise.
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
_logger.warning("Location with name '%s' does not exists in the system")
|
||||||
|
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _get_shop_and_location_id(self, orderType, location_name, order_type_record):
|
||||||
|
_logger.info("\n _get_shop_and_location_id().... Called.....")
|
||||||
|
_logger.info("orderType %s",orderType)
|
||||||
|
_logger.info("location_name %s", location_name)
|
||||||
|
OrderTypeShopMap = self.env['order.type.shop.map']
|
||||||
|
SaleShop = self.env['sale.shop']
|
||||||
|
shop_list_with_order_type = None
|
||||||
|
if location_name:
|
||||||
|
shop_list_with_order_type = OrderTypeShopMap.search(
|
||||||
|
[('order_type', '=', order_type_record.id), ('location_name', '=', location_name)])
|
||||||
|
_logger.info("\nFor specified order location name [%s], shop_list_with_orderType : %s",
|
||||||
|
location_name, shop_list_with_order_type)
|
||||||
|
|
||||||
|
if not shop_list_with_order_type:
|
||||||
|
_logger.info("\nCouldn't identify OrderType-Shop mapping for specified order location name [%s], "
|
||||||
|
"searching for default OrderType-Shop map", location_name)
|
||||||
|
shop_list_with_order_type = OrderTypeShopMap.search(
|
||||||
|
[('order_type', '=', order_type_record.id), ('location_name', '=', None)])
|
||||||
|
_logger.info("\nOrderType-Shop mappings without order location name specified: %s",
|
||||||
|
shop_list_with_order_type)
|
||||||
|
|
||||||
|
if not shop_list_with_order_type:
|
||||||
|
_logger.info("\nCouldn't identify OrderType-Shop mapping for Order Type [%s]", orderType)
|
||||||
|
return False, False
|
||||||
|
|
||||||
|
order_shop_map_object = shop_list_with_order_type[0]
|
||||||
|
_logger.info("Identified Order Shop mapping %s", order_shop_map_object)
|
||||||
|
if order_shop_map_object.shop_id:
|
||||||
|
shop_id = order_shop_map_object.shop_id.id
|
||||||
|
else:
|
||||||
|
shop_records = SaleShop.search([])
|
||||||
|
first_shop = shop_records[0]
|
||||||
|
shop_id = first_shop.id
|
||||||
|
|
||||||
|
if order_shop_map_object.location_id:
|
||||||
|
location_id = order_shop_map_object.location_id.id
|
||||||
|
else:
|
||||||
|
location_id = SaleShop.search([('id','=',shop_id)]).location_id.id
|
||||||
|
|
||||||
|
_logger.info("\n__get_shop_and_location_id() returning shop_id: %s, location_id: %s", shop_id, location_id)
|
||||||
|
return shop_id, location_id
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def create_orders(self, vals):
|
||||||
|
customer_id = vals.get("customer_id")
|
||||||
|
location_name = vals.get("locationName")
|
||||||
|
all_orders = self._get_openerp_orders(vals)
|
||||||
|
|
||||||
|
if not all_orders:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
customer_ids = self.env['res.partner'].search([('ref', '=', customer_id)])
|
||||||
|
if customer_ids:
|
||||||
|
cus_id = customer_ids[0]
|
||||||
|
|
||||||
|
for orderType, ordersGroup in groupby(all_orders, lambda order: order.get('type')):
|
||||||
|
|
||||||
|
order_type_def = self.env['order.type'].search([('name','=',orderType)])
|
||||||
|
if (not order_type_def):
|
||||||
|
_logger.info("\nOrder Type is not defined. Ignoring %s for Customer %s",orderType,cus_id)
|
||||||
|
continue
|
||||||
|
|
||||||
|
orders = list(ordersGroup)
|
||||||
|
care_setting = orders[0].get('visitType').lower()
|
||||||
|
provider_name = orders[0].get('providerName')
|
||||||
|
# will return order line data for products which exists in the system, either with productID passed
|
||||||
|
# or with conceptName
|
||||||
|
unprocessed_orders = self._filter_processed_orders(orders)
|
||||||
|
_logger.info("\n DEBUG: Unprocessed Orders: %s", unprocessed_orders)
|
||||||
|
shop_id, location_id = self._get_shop_and_location_id(orderType, location_name, order_type_def)
|
||||||
|
# shop_id = tup[0]
|
||||||
|
# location_id = tup[1]
|
||||||
|
|
||||||
|
if (not shop_id):
|
||||||
|
err_message = "Can not process order. Order type:{} - should be matched to a shop".format(orderType)
|
||||||
|
_logger.info(err_message)
|
||||||
|
raise Warning(err_message)
|
||||||
|
|
||||||
|
|
||||||
|
shop_obj = self.env['sale.shop'].search([('id','=',shop_id)])
|
||||||
|
warehouse_id = shop_obj.warehouse_id.id
|
||||||
|
_logger.warning("warehouse_id: %s"%(warehouse_id))
|
||||||
|
|
||||||
|
name = self.env['ir.sequence'].next_by_code('sale.order')
|
||||||
|
#Adding both the ids to the unprocessed array of orders, Separating to dispensed and non-dispensed orders
|
||||||
|
unprocessed_dispensed_order = []
|
||||||
|
unprocessed_non_dispensed_order = []
|
||||||
|
for unprocessed_order in unprocessed_orders:
|
||||||
|
unprocessed_order['location_id'] = location_id
|
||||||
|
unprocessed_order['warehouse_id'] = warehouse_id
|
||||||
|
if(unprocessed_order.get('dispensed', 'false') == 'true'):
|
||||||
|
unprocessed_dispensed_order.append(unprocessed_order)
|
||||||
|
else:
|
||||||
|
unprocessed_non_dispensed_order.append(unprocessed_order)
|
||||||
|
|
||||||
|
if(len(unprocessed_non_dispensed_order) > 0):
|
||||||
|
_logger.debug("\n Processing Unprocessed non dispensed Orders: %s", list(unprocessed_non_dispensed_order))
|
||||||
|
sale_order_ids = self.env['sale.order'].search([('partner_id', '=', cus_id.id),
|
||||||
|
('shop_id', '=', shop_id),
|
||||||
|
('state', '=', 'draft'),
|
||||||
|
('origin', '=', 'ATOMFEED SYNC')])
|
||||||
|
if(not sale_order_ids):
|
||||||
|
# Non Dispensed New
|
||||||
|
# replaced create_sale_order method call
|
||||||
|
_logger.debug("\n No existing sale order for Unprocessed non dispensed Orders. Creating .. ")
|
||||||
|
sale_order_vals = {'partner_id': cus_id.id,
|
||||||
|
'location_id': unprocessed_non_dispensed_order[0]['location_id'],
|
||||||
|
'warehouse_id': unprocessed_non_dispensed_order[0]['warehouse_id'],
|
||||||
|
'care_setting': care_setting,
|
||||||
|
'provider_name': provider_name,
|
||||||
|
'date_order': datetime.strftime(datetime.now(), DTF),
|
||||||
|
'pricelist_id': cus_id.property_product_pricelist and cus_id.property_product_pricelist.id or False,
|
||||||
|
'payment_term_id': shop_obj.payment_default_id.id,
|
||||||
|
'project_id': shop_obj.project_id.id if shop_obj.project_id else False,
|
||||||
|
'picking_policy': 'direct',
|
||||||
|
'state': 'draft',
|
||||||
|
'shop_id': shop_id,
|
||||||
|
'origin': 'ATOMFEED SYNC',
|
||||||
|
}
|
||||||
|
if shop_obj.pricelist_id:
|
||||||
|
sale_order_vals.update({'pricelist_id': shop_obj.pricelist_id.id})
|
||||||
|
sale_order = self.env['sale.order'].create(sale_order_vals)
|
||||||
|
_logger.debug("\n Created a new Sale Order for non dispensed orders. ID: %s. Processing order lines ..", sale_order.id)
|
||||||
|
for rec in unprocessed_non_dispensed_order:
|
||||||
|
self._process_orders(sale_order, unprocessed_non_dispensed_order, rec)
|
||||||
|
else:
|
||||||
|
# Non Dispensed Update
|
||||||
|
# replaced update_sale_order method call
|
||||||
|
for order in sale_order_ids:
|
||||||
|
order.write({'care_setting': care_setting, 'provider_name': provider_name})
|
||||||
|
for rec in unprocessed_non_dispensed_order:
|
||||||
|
self._process_orders(order, unprocessed_non_dispensed_order, rec)
|
||||||
|
# break from the outer loop
|
||||||
|
break
|
||||||
|
|
||||||
|
# sale_order_ids_for_dispensed = self.env['sale.order'].search([('partner_id', '=', cus_id.id),
|
||||||
|
# ('shop_id', '=', shop_id),
|
||||||
|
# ('state', '=', 'draft'),
|
||||||
|
# ('origin', '=', 'ATOMFEED SYNC')])
|
||||||
|
#
|
||||||
|
# if (len(sale_order_ids_for_dispensed) > 0):
|
||||||
|
# if (sale_order_ids_for_dispensed[0]):
|
||||||
|
# sale_order_line_ids_for_dispensed = self.env['sale.order.line'].search(
|
||||||
|
# [('order_id', '=', sale_order_ids_for_dispensed[0])])
|
||||||
|
# if (len(sale_order_line_ids_for_dispensed) != 0):
|
||||||
|
# for so_ids in sale_order_line_ids_for_dispensed:
|
||||||
|
# so_ids.unlink()
|
||||||
|
|
||||||
|
if (len(unprocessed_dispensed_order) > 0):
|
||||||
|
_logger.debug("\n Processing Unprocessed dispensed Orders: %s", list(unprocessed_dispensed_order))
|
||||||
|
auto_convert_dispensed = self.env['ir.values'].search([('model', '=', 'sale.config.settings'),
|
||||||
|
('name', '=', 'convert_dispensed')]).value
|
||||||
|
auto_invoice_dispensed = self.env.ref('bahmni_sale.auto_register_invoice_payment_for_dispensed').value
|
||||||
|
|
||||||
|
|
||||||
|
sale_order_ids = self.env['sale.order'].search([('partner_id', '=', cus_id.id),
|
||||||
|
('shop_id', '=', shop_id),
|
||||||
|
('state', '=', 'draft'),
|
||||||
|
('origin', '=', 'ATOMFEED SYNC')])
|
||||||
|
|
||||||
|
if any(sale_order_ids):
|
||||||
|
_logger.debug("\n For exsiting sale orders for the shop, trying to unlink any openmrs order if any")
|
||||||
|
self._unlink_sale_order_lines_and_remove_empty_orders(sale_order_ids,unprocessed_dispensed_order)
|
||||||
|
|
||||||
|
sale_order_ids_for_dispensed = self.env['sale.order'].search([('partner_id', '=', cus_id.id),
|
||||||
|
('shop_id', '=', shop_id),
|
||||||
|
('location_id', '=', location_id),
|
||||||
|
('state', '=', 'draft'),
|
||||||
|
('origin', '=', 'ATOMFEED SYNC')])
|
||||||
|
|
||||||
|
if not sale_order_ids_for_dispensed:
|
||||||
|
_logger.debug("\n Could not find any sale_order at specified shop and stock location. Creating a new Sale order for dispensed orders")
|
||||||
|
|
||||||
|
# TODO: commenting off anand's code for now
|
||||||
|
# dispensed = False
|
||||||
|
# for order in unprocessed_dispensed_order:
|
||||||
|
# for line in sale_order_line_ids:
|
||||||
|
# if order.get('orderId') == line.external_order_id and order.get('dispensed')!='false':
|
||||||
|
# line.write({'dispensed':True})
|
||||||
|
# dispensed = True
|
||||||
|
# if dispensed:
|
||||||
|
# auto_convert_set = self.env['ir.values'].search([('model', '=', 'sale.config.settings'),
|
||||||
|
# ('name', '=', 'convert_dispensed')]).value
|
||||||
|
# if auto_convert_set:
|
||||||
|
# sale_order_ids.action_confirm()
|
||||||
|
# sale_order_ids.validate_payment()
|
||||||
|
|
||||||
|
sale_order_dict = {'partner_id': cus_id.id,
|
||||||
|
'location_id': location_id,
|
||||||
|
'warehouse_id': warehouse_id,
|
||||||
|
'care_setting': care_setting,
|
||||||
|
'provider_name': provider_name,
|
||||||
|
'date_order': datetime.strftime(datetime.now(), DTF),
|
||||||
|
'pricelist_id': cus_id.property_product_pricelist and cus_id.property_product_pricelist.id or False,
|
||||||
|
'payment_term_id': shop_obj.payment_default_id.id,
|
||||||
|
'project_id': shop_obj.project_id.id if shop_obj.project_id else False,
|
||||||
|
'picking_policy': 'direct',
|
||||||
|
'state': 'draft',
|
||||||
|
'shop_id': shop_id,
|
||||||
|
'origin': 'ATOMFEED SYNC'}
|
||||||
|
if shop_obj.pricelist_id:
|
||||||
|
sale_order_dict.update({'pricelist_id': shop_obj.pricelist_id.id})
|
||||||
|
new_sale_order = self.env['sale.order'].create(sale_order_dict)
|
||||||
|
_logger.debug("\n Created a new Sale Order. ID: %s. Processing order lines ..", new_sale_order.id)
|
||||||
|
for line in unprocessed_dispensed_order:
|
||||||
|
self._process_orders(new_sale_order, unprocessed_dispensed_order, line)
|
||||||
|
|
||||||
|
if auto_convert_dispensed:
|
||||||
|
_logger.debug("\n Confirming delivery and payment for the newly created sale order..")
|
||||||
|
new_sale_order.auto_validate_delivery()
|
||||||
|
if auto_invoice_dispensed == '1':
|
||||||
|
new_sale_order.validate_payment()
|
||||||
|
|
||||||
|
else:
|
||||||
|
_logger.debug("\n There are other sale_orders at specified shop and stock location.")
|
||||||
|
sale_order_to_process = None
|
||||||
|
if not auto_convert_dispensed:
|
||||||
|
# try to find an existing sale order to add the openmrs orders to
|
||||||
|
if any(sale_order_ids_for_dispensed):
|
||||||
|
_logger.debug("\n Found a sale order to append dispensed lines. ID : %s",sale_order_ids_for_dispensed[0].id)
|
||||||
|
sale_order_to_process = sale_order_ids_for_dispensed[0]
|
||||||
|
|
||||||
|
if not sale_order_to_process:
|
||||||
|
# create new sale order
|
||||||
|
_logger.debug("\n Post unlinking of order lines. Could not find a sale order to append dispensed lines. Creating .. ")
|
||||||
|
sales_order_obj = {'partner_id': cus_id.id,
|
||||||
|
'location_id': location_id,
|
||||||
|
'warehouse_id': warehouse_id,
|
||||||
|
'care_setting': care_setting,
|
||||||
|
'provider_name': provider_name,
|
||||||
|
'date_order': datetime.strftime(datetime.now(), DTF),
|
||||||
|
'pricelist_id': cus_id.property_product_pricelist and cus_id.property_product_pricelist.id or False,
|
||||||
|
'payment_term_id': shop_obj.payment_default_id.id,
|
||||||
|
'project_id': shop_obj.project_id.id if shop_obj.project_id else False,
|
||||||
|
'picking_policy': 'direct',
|
||||||
|
'state': 'draft',
|
||||||
|
'shop_id': shop_id,
|
||||||
|
'origin': 'ATOMFEED SYNC'}
|
||||||
|
|
||||||
|
if shop_obj.pricelist_id:
|
||||||
|
sales_order_obj.update({'pricelist_id': shop_obj.pricelist_id.id})
|
||||||
|
sale_order_to_process = self.env['sale.order'].create(sales_order_obj)
|
||||||
|
_logger.info("\n DEBUG: Created a new Sale Order. ID: %s", sale_order_to_process.id)
|
||||||
|
|
||||||
|
_logger.debug("\n Processing dispensed lines. Appending to Order ID %s", sale_order_to_process.id)
|
||||||
|
for line in unprocessed_dispensed_order:
|
||||||
|
self._process_orders(sale_order_to_process, unprocessed_dispensed_order, line)
|
||||||
|
|
||||||
|
if auto_convert_dispensed and sale_order_to_process:
|
||||||
|
_logger.debug("\n Confirming delivery and payment ..")
|
||||||
|
sale_order_to_process.auto_validate_delivery()
|
||||||
|
# TODO: payment validation checks
|
||||||
|
# TODO: 1) Should be done through a config "sale.config.settings"[auto_invoice_dispensed]"
|
||||||
|
# TODO: 2) Should check the invoice amount. Odoo fails/throws-error if the invoice amount is 0.
|
||||||
|
if auto_invoice_dispensed == '1':
|
||||||
|
sale_order_to_process.validate_payment()
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise Warning("Patient Id not found in Odoo")
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _remove_existing_sale_order_line(self, sale_order_id, unprocessed_dispensed_order):
|
||||||
|
sale_order_lines = self.env['sale.order.line'].search([('order_id', '=', sale_order_id.id)])
|
||||||
|
sale_order_lines_to_be_saved = []
|
||||||
|
sale_order_lines_unliked = []
|
||||||
|
for order in unprocessed_dispensed_order:
|
||||||
|
for sale_order_line in sale_order_lines:
|
||||||
|
if(order['orderId'] == sale_order_line.external_order_id):
|
||||||
|
if order.get('dispensed')=='false':
|
||||||
|
dispensed = False
|
||||||
|
else:
|
||||||
|
dispensed = True
|
||||||
|
if(dispensed != sale_order_line.dispensed):
|
||||||
|
sale_order_lines_to_be_saved.append(sale_order_line)
|
||||||
|
|
||||||
|
for rec in sale_order_lines_to_be_saved:
|
||||||
|
sale_order_lines_unliked.append(rec.id)
|
||||||
|
rec.unlink()
|
||||||
|
|
||||||
|
return sale_order_lines_unliked
|
||||||
|
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _process_orders(self, sale_order, all_orders, order):
|
||||||
|
external_order_id = order['orderId']
|
||||||
|
order_dispensed = order.get('dispensed', ORDER_DISPENSED_FALSE)
|
||||||
|
# order_in_db = self.env['sale.order.line'].search([('external_order_id', '=', external_order_id)])
|
||||||
|
# if(order_in_db or self._order_already_processed(external_order_id, order_dispensed)):
|
||||||
|
# return
|
||||||
|
if self._order_already_processed(external_order_id, order_dispensed):
|
||||||
|
return
|
||||||
|
|
||||||
|
parent_order_line = []
|
||||||
|
# if(order.get('previousOrderId', False) and order.get('dispensed', "") == "true"):
|
||||||
|
# self._create_sale_order_line(cr, uid, name, sale_order, order, context)
|
||||||
|
|
||||||
|
if order.get('previousOrderId', False) and order_dispensed == 'false':
|
||||||
|
parent_order = self._fetch_parent(all_orders, order)
|
||||||
|
if(parent_order):
|
||||||
|
self._process_orders(sale_order, all_orders, parent_order)
|
||||||
|
parent_order_line = self.env['sale.order.line'].search([('external_order_id', '=', order['previousOrderId'])])
|
||||||
|
if(not parent_order_line and not self._order_already_processed(order['previousOrderId'], order_dispensed)):
|
||||||
|
raise Warning("Previous order id does not exist in DB. This can be because of previous failed events")
|
||||||
|
|
||||||
|
if(order["voided"] or order.get('action', "") == "DISCONTINUE"):
|
||||||
|
self._delete_sale_order_line(parent_order_line)
|
||||||
|
elif(order.get('action', "") == "REVISE" and order_dispensed == "false"):
|
||||||
|
self._update_sale_order_line(sale_order.id, order, parent_order_line)
|
||||||
|
else:
|
||||||
|
self._create_sale_order_line(sale_order.id, order)
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _delete_sale_order_line(self, parent_order_line):
|
||||||
|
if(parent_order_line):
|
||||||
|
if(parent_order_line[0] and parent_order_line[0].order_id.state == 'draft'):
|
||||||
|
for parent in parent_order_line:
|
||||||
|
parent.unlink()
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _update_sale_order_line(self, sale_order, order, parent_order_line):
|
||||||
|
self._delete_sale_order_line(parent_order_line)
|
||||||
|
self._create_sale_order_line(sale_order, order)
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _create_sale_order_line(self, sale_order, order):
|
||||||
|
if self._order_already_processed(order['orderId'], order.get('dispensed', ORDER_DISPENSED_FALSE)):
|
||||||
|
return
|
||||||
|
self._create_sale_order_line_function(sale_order, order)
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _get_order_quantity(self, order, default_quantity_value):
|
||||||
|
if(not self.env['syncable.units.mapping'].search([('name', '=', order['quantityUnits'])])):
|
||||||
|
return default_quantity_value
|
||||||
|
return order['quantity']
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _get_order_line_uom(self, order_line, product_default_uom):
|
||||||
|
|
||||||
|
uom_ids = self.env['syncable.units.mapping'].search([('name', '=', order_line['quantityUnits'])])
|
||||||
|
if(uom_ids):
|
||||||
|
uom_id = uom_ids.ids[0]
|
||||||
|
uom_obj = self.env['syncable.units.mapping'].browse(uom_id)
|
||||||
|
if(uom_obj.unit_of_measure):
|
||||||
|
return uom_obj.unit_of_measure.id
|
||||||
|
return product_default_uom
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _create_sale_order_line_function(self, sale_order, order):
|
||||||
|
stored_prod_ids = self._get_product_ids(order)
|
||||||
|
|
||||||
|
if(stored_prod_ids):
|
||||||
|
prod_id = stored_prod_ids[0]
|
||||||
|
prod_obj = self.env['product.product'].browse(prod_id)
|
||||||
|
sale_order_line_obj = self.env['sale.order.line']
|
||||||
|
prod_lot = sale_order_line_obj.get_available_batch_details(prod_id, sale_order)
|
||||||
|
|
||||||
|
actual_quantity = order['quantity']
|
||||||
|
comments = " ".join([str(actual_quantity), str(order.get('quantityUnits', None))])
|
||||||
|
|
||||||
|
default_quantity_total = self.env.ref('bahmni_sale.group_default_quantity')
|
||||||
|
_logger.info("DEFAULT QUANTITY TOTAL")
|
||||||
|
_logger.info(default_quantity_total)
|
||||||
|
default_quantity_value = 1
|
||||||
|
if default_quantity_total and len(default_quantity_total.users) > 0:
|
||||||
|
default_quantity_value = -1
|
||||||
|
|
||||||
|
order['quantity'] = self._get_order_quantity(order, default_quantity_value)
|
||||||
|
order_line_uom = self._get_order_line_uom(order, prod_obj.uom_id.id)
|
||||||
|
product_uom_qty = order['quantity']
|
||||||
|
if(prod_lot != None and order['quantity'] > prod_lot.stock_forecast):
|
||||||
|
product_uom_qty = prod_lot.stock_forecast
|
||||||
|
description = " ".join([prod_obj.name, "- Total", str(product_uom_qty), str(order.get('quantityUnits', None))])
|
||||||
|
order_line_dispensed = True if order.get('dispensed') == 'true' or (order.get('dispensed') and order.get('dispensed') != 'false') else False
|
||||||
|
sale_order_line = {
|
||||||
|
'product_id': prod_id,
|
||||||
|
'price_unit': prod_obj.list_price,
|
||||||
|
'comments': comments,
|
||||||
|
'product_uom_qty': product_uom_qty,
|
||||||
|
'product_uom': order_line_uom,
|
||||||
|
'order_id': sale_order,
|
||||||
|
'external_id': order['encounterId'],
|
||||||
|
'external_order_id': order['orderId'],
|
||||||
|
'name': description,
|
||||||
|
'type': 'make_to_stock',
|
||||||
|
'state': 'draft',
|
||||||
|
'dispensed': order_line_dispensed
|
||||||
|
}
|
||||||
|
|
||||||
|
if prod_lot != None:
|
||||||
|
life_date = prod_lot.life_date and datetime.strptime(prod_lot.life_date, DTF)
|
||||||
|
if self.env.ref('bahmni_sale.sale_price_basedon_cost_price_markup').value == '1':
|
||||||
|
sale_order_line['price_unit'] = prod_lot.sale_price if prod_lot.sale_price > 0.0 else sale_order_line['price_unit']
|
||||||
|
sale_order_line['batch_name'] = prod_lot.name
|
||||||
|
sale_order_line['batch_id'] = prod_lot.id
|
||||||
|
sale_order_line['expiry_date'] = life_date and life_date.strftime(DTF)
|
||||||
|
|
||||||
|
|
||||||
|
sale_obj = self.env['sale.order'].browse(sale_order)
|
||||||
|
sale_line = sale_order_line_obj.create(sale_order_line)
|
||||||
|
|
||||||
|
sale_line._compute_tax_id()
|
||||||
|
if sale_obj.pricelist_id:
|
||||||
|
line_product = prod_obj.with_context(
|
||||||
|
lang = sale_obj.partner_id.lang,
|
||||||
|
partner = sale_obj.partner_id.id,
|
||||||
|
quantity = sale_line.product_uom_qty,
|
||||||
|
date = sale_obj.date_order,
|
||||||
|
pricelist = sale_obj.pricelist_id.id,
|
||||||
|
uom = prod_obj.uom_id.id
|
||||||
|
)
|
||||||
|
price = self.env['account.tax']._fix_tax_included_price_company(sale_line._get_display_price(prod_obj), prod_obj.taxes_id, sale_line.tax_id, sale_line.company_id)
|
||||||
|
sale_line.price_unit = price
|
||||||
|
|
||||||
|
if product_uom_qty != order['quantity']:
|
||||||
|
order['quantity'] = order['quantity'] - product_uom_qty
|
||||||
|
self._create_sale_order_line_function(sale_order, order)
|
||||||
|
|
||||||
|
def _fetch_parent(self, all_orders, child_order):
|
||||||
|
for order in all_orders:
|
||||||
|
if(order.get("orderId") == child_order.get("previousOrderId")):
|
||||||
|
return order
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _is_order_revised_processed(self, all_orders, order_to_process):
|
||||||
|
parent_order_line = None
|
||||||
|
for order in all_orders:
|
||||||
|
if order.get('previousOrderId', '') == order_to_process.get('orderId'):
|
||||||
|
parent_order_line = self.env['sale.order.line'].search([('external_order_id', '=', order.get('orderId'))])
|
||||||
|
break
|
||||||
|
return True if parent_order_line and any(parent_order_line) else False
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _filter_processed_orders(self, orders):
|
||||||
|
unprocessed_orders = []
|
||||||
|
# sort the orders so that the revised ones appear later
|
||||||
|
orders.sort(key=lambda order_item: 1 if order_item.get('previousOrderId', '') == '' else 2)
|
||||||
|
for order in orders:
|
||||||
|
if self._is_order_revised_processed(orders, order):
|
||||||
|
continue
|
||||||
|
dispensed_status = order.get('dispensed') == 'true'
|
||||||
|
existing_order_line = self.env['sale.order.line'].search([('external_order_id', '=', order['orderId'])])
|
||||||
|
if not existing_order_line:
|
||||||
|
unprocessed_orders.append(order)
|
||||||
|
else:
|
||||||
|
sale_order_line = existing_order_line[0]
|
||||||
|
if not sale_order_line.dispensed and dispensed_status:
|
||||||
|
unprocessed_orders.append(order)
|
||||||
|
|
||||||
|
# sale_order_line = self.env['sale.order.line'].search([('external_order_id', '=', order['orderId']), ('dispensed', '=', False)])
|
||||||
|
# if not sale_order_line:
|
||||||
|
# unprocessed_orders.append(order)
|
||||||
|
return self._filter_products_undefined(unprocessed_orders)
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _order_already_processed(self, external_order_id, dispensed_status):
|
||||||
|
dispensed = True if dispensed_status == 'true' else False
|
||||||
|
existing_order_line = self.env['sale.order.line'].search([('external_order_id', '=', external_order_id)])
|
||||||
|
if not existing_order_line:
|
||||||
|
return False
|
||||||
|
elif any(existing_order_line):
|
||||||
|
sale_order = self.env['sale.order'].search([('id', '=', existing_order_line[0].order_id.id)])
|
||||||
|
_logger.info("\n Checking for order line's parent Order state")
|
||||||
|
if sale_order[0].state != 'draft':
|
||||||
|
return True
|
||||||
|
return existing_order_line[0].dispensed == dispensed
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _filter_products_undefined(self, orders):
|
||||||
|
products_in_system = []
|
||||||
|
|
||||||
|
for order in orders:
|
||||||
|
stored_prod_ids = self._get_product_ids(order)
|
||||||
|
if(stored_prod_ids):
|
||||||
|
products_in_system.append(order)
|
||||||
|
return products_in_system
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _get_product_ids(self, order):
|
||||||
|
if order['productId']:
|
||||||
|
prod_ids = self.env['product.product'].search([('uuid', '=', order['productId'])])
|
||||||
|
else:
|
||||||
|
prod_ids = self.env['product.product'].search([('name', '=', order['conceptName'])])
|
||||||
|
return prod_ids.ids
|
||||||
|
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _unlink_sale_order_lines_and_remove_empty_orders(self, sale_orders, openmrs_orders):
|
||||||
|
for existing_sale_order in sale_orders:
|
||||||
|
_logger.info("\n DEBUG: checking existing sale order for any older order_lines. ID : %s", existing_sale_order.id)
|
||||||
|
# Remove existing sale order line
|
||||||
|
if not any(self._remove_existing_sale_order_line(existing_sale_order, openmrs_orders)):
|
||||||
|
continue
|
||||||
|
# Removing existing empty sale order
|
||||||
|
exisiting_sale_order_lines = self.env['sale.order.line'].search([('order_id', '=', existing_sale_order.id)])
|
||||||
|
if not exisiting_sale_order_lines or not any(exisiting_sale_order_lines):
|
||||||
|
_logger.info("\n DEBUG: Removing Empty Sale Order. ID : %s", existing_sale_order.id)
|
||||||
|
existing_sale_order.unlink()
|
BIN
bahmni-addons/bahmni_atom_feed/models/order_save_service.pyc
Normal file
BIN
bahmni-addons/bahmni_atom_feed/models/order_save_service.pyc
Normal file
Binary file not shown.
12
bahmni-addons/bahmni_atom_feed/models/order_type.py
Normal file
12
bahmni-addons/bahmni_atom_feed/models/order_type.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from odoo import models, fields, api
|
||||||
|
|
||||||
|
|
||||||
|
class OrderType(models.Model):
|
||||||
|
_name = 'order.type'
|
||||||
|
|
||||||
|
name = fields.Char(string='Name')
|
||||||
|
|
||||||
|
_sql_constraints = [('unique_name', 'unique(name)',
|
||||||
|
'Order type with this name already exists!')]
|
||||||
|
|
BIN
bahmni-addons/bahmni_atom_feed/models/order_type.pyc
Normal file
BIN
bahmni-addons/bahmni_atom_feed/models/order_type.pyc
Normal file
Binary file not shown.
18
bahmni-addons/bahmni_atom_feed/models/order_type_shop_map.py
Normal file
18
bahmni-addons/bahmni_atom_feed/models/order_type_shop_map.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
|
||||||
|
from odoo import fields, models, api, _
|
||||||
|
|
||||||
|
class order_type_shop_map(models.Model):
|
||||||
|
_name = "order.type.shop.map"
|
||||||
|
_description = "Order Type to Shop Mapping"
|
||||||
|
|
||||||
|
order_type = fields.Many2one('order.type','Order Type', required=True)
|
||||||
|
shop_id = fields.Many2one('sale.shop', 'Shop', required=True)
|
||||||
|
location_name = fields.Char('Order Location Name')
|
||||||
|
location_id = fields.Many2one('stock.location', 'Location Name')
|
||||||
|
|
||||||
|
@api.onchange('shop_id')
|
||||||
|
def onchange_shop_id(self):
|
||||||
|
self.location_id = self.shop_id.location_id.id
|
||||||
|
|
||||||
|
|
||||||
|
|
BIN
bahmni-addons/bahmni_atom_feed/models/order_type_shop_map.pyc
Normal file
BIN
bahmni-addons/bahmni_atom_feed/models/order_type_shop_map.pyc
Normal file
Binary file not shown.
67
bahmni-addons/bahmni_atom_feed/models/product_uom_service.py
Normal file
67
bahmni-addons/bahmni_atom_feed/models/product_uom_service.py
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from odoo import models, fields, api
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ProductUomService(models.Model):
|
||||||
|
_name = 'product.uom.service'
|
||||||
|
_auto = False
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _fill_product_uom(self, product_uom_from_feed):
|
||||||
|
product_uom = {}
|
||||||
|
category = product_uom_from_feed.get("category")
|
||||||
|
category_from_db = self.env["product.uom.categ"].search([('uuid', '=', category["id"])],
|
||||||
|
limit=1)
|
||||||
|
|
||||||
|
product_uom["name"] = product_uom_from_feed.get("name")
|
||||||
|
product_uom["uuid"] = product_uom_from_feed.get("id")
|
||||||
|
product_uom["active"] = product_uom_from_feed.get("isActive")
|
||||||
|
ratio = float(product_uom_from_feed.get("ratio"))
|
||||||
|
product_uom["factor"] = 1/ratio
|
||||||
|
product_uom["category_id"] = category_from_db.id if category_from_db else False
|
||||||
|
|
||||||
|
uom_type = "reference"
|
||||||
|
if ratio > 1:
|
||||||
|
uom_type = "bigger"
|
||||||
|
elif ratio < 1:
|
||||||
|
uom_type = "smaller"
|
||||||
|
|
||||||
|
product_uom["uom_type"] = uom_type
|
||||||
|
return product_uom
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def create_or_update_product_uom(self, vals):
|
||||||
|
product_uom = json.loads(vals.get("product_uom"))
|
||||||
|
object_ids = self.env["product.uom"].with_context({"active_test": False}).search([('uuid', '=', product_uom.get("id"))], limit=1)
|
||||||
|
uom = self._fill_product_uom(product_uom)
|
||||||
|
|
||||||
|
if object_ids:
|
||||||
|
return object_ids.write(uom)
|
||||||
|
|
||||||
|
_logger.info("\ninserted : uom :\n")
|
||||||
|
_logger.info(object_ids)
|
||||||
|
_logger.info(uom)
|
||||||
|
return self.env['product.uom'].create(uom)
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def create_or_update_product_uom_category(self, vals):
|
||||||
|
product_uom_categ = json.loads(vals.get("product_uom_category"))
|
||||||
|
uom_categ = {}
|
||||||
|
uom_categ["name"] = product_uom_categ.get("name")
|
||||||
|
uom_categ["uuid"] = product_uom_categ.get("id")
|
||||||
|
object_ids = self.env["product.uom.categ"].search([('uuid', '=', uom_categ["uuid"])],
|
||||||
|
limit=1)
|
||||||
|
|
||||||
|
if object_ids:
|
||||||
|
_logger.info("\nupdated : uom_categ:\n")
|
||||||
|
_logger.info(uom_categ)
|
||||||
|
return object_ids.write(uom_categ)
|
||||||
|
|
||||||
|
_logger.info("\ninserted : uom_categ:\n")
|
||||||
|
_logger.info(uom_categ)
|
||||||
|
return self.env['product.uom.categ'].create(uom_categ)
|
BIN
bahmni-addons/bahmni_atom_feed/models/product_uom_service.pyc
Normal file
BIN
bahmni-addons/bahmni_atom_feed/models/product_uom_service.pyc
Normal file
Binary file not shown.
@ -0,0 +1,75 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from odoo import models, fields, api
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ReferenceDataService(models.Model):
|
||||||
|
_name = 'reference.data.service'
|
||||||
|
_auto = False
|
||||||
|
_description = 'Intermediate Class for creating products, through api call'
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def create_or_update_ref_data(self, vals, category_name):
|
||||||
|
product_obj = self.env["product.product"].with_context(
|
||||||
|
{"active_test": False}).search([('uuid', '=', vals.get("uuid"))], limit=1)
|
||||||
|
updated_data = self._fill_data(vals, category=category_name)
|
||||||
|
if product_obj:
|
||||||
|
product_obj.write(updated_data)
|
||||||
|
else:
|
||||||
|
self.env['product.product'].create(updated_data)
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _fill_data(self, vals, category=None):
|
||||||
|
data = {}
|
||||||
|
category_name = category if category else "Others"
|
||||||
|
category_hierarchy = self._get_category_hierarchy(category_name)
|
||||||
|
specified_product_category = vals.get("product_category")
|
||||||
|
if specified_product_category:
|
||||||
|
category_name = specified_product_category
|
||||||
|
_logger.info("\n******* Creating Product with Category: %s, hierarchy: %s", category_name, category_hierarchy)
|
||||||
|
category_obj = self.env['product.category'].search([('name', '=', category_name)])
|
||||||
|
if category_obj.read():
|
||||||
|
category_from_db = category_obj.read()[0]
|
||||||
|
categ_id = category_from_db and category_from_db.get('id') or \
|
||||||
|
self._create_category_in_hierarchy(category_name, category_hierarchy).id
|
||||||
|
else:
|
||||||
|
categ_id = self.env['product.category'].create({'name': category_name}).id
|
||||||
|
|
||||||
|
data["uuid"] = vals.get("uuid")
|
||||||
|
data["name"] = vals.get("name")
|
||||||
|
data["active"] = vals.get("is_active")
|
||||||
|
data["categ_id"] = categ_id
|
||||||
|
data["sale_ok"] = vals.get("is_active")
|
||||||
|
data["purchase_ok"] = False
|
||||||
|
data["type"] = "service"
|
||||||
|
return data
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _get_category_hierarchy(self, category):
|
||||||
|
if category == 'Radiology':
|
||||||
|
return ["Services", "All Products"]
|
||||||
|
elif category == 'Test':
|
||||||
|
return ["Lab", "Services", "All Products"]
|
||||||
|
elif category == 'Panel':
|
||||||
|
return ["Lab", "Services", "All Products"]
|
||||||
|
else:
|
||||||
|
return ["Services", "All"]
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _create_category_in_hierarchy(self, category_name, category_hierarchy):
|
||||||
|
_logger.info("\n creating product category %s, under hierarchy %s", category_name, category_hierarchy)
|
||||||
|
if len(category_hierarchy) > 0:
|
||||||
|
category_ids = self.env['product.category'].search([('name', '=', category_hierarchy[0])]).ids
|
||||||
|
if len(category_ids) > 0:
|
||||||
|
parent_id = category_ids[0]
|
||||||
|
else:
|
||||||
|
parent_category_name = category_hierarchy[0]
|
||||||
|
del category_hierarchy[0]
|
||||||
|
parent_id = self._create_category_in_hierarchy(parent_category_name, category_hierarchy)
|
||||||
|
return self.env['product.category'].create({'name': category_name,
|
||||||
|
'parent_id': parent_id})
|
||||||
|
else:
|
||||||
|
return self.env['product.category'].create({'name': category_name})
|
BIN
bahmni-addons/bahmni_atom_feed/models/reference_data_service.pyc
Normal file
BIN
bahmni-addons/bahmni_atom_feed/models/reference_data_service.pyc
Normal file
Binary file not shown.
27
bahmni-addons/bahmni_atom_feed/models/res_company.py
Normal file
27
bahmni-addons/bahmni_atom_feed/models/res_company.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from odoo import models, fields
|
||||||
|
|
||||||
|
|
||||||
|
class ResCompany(models.Model):
|
||||||
|
_inherit = 'res.company'
|
||||||
|
|
||||||
|
dhis2_code = fields.Char(string="DHIS Code")
|
||||||
|
|
||||||
|
_header_main_wo_placeholders = """
|
||||||
|
<header>
|
||||||
|
<pageTemplate>
|
||||||
|
<frame id="first" x1="1.3cm" y1="3.0cm" height="21.7cm" width="19.0cm"/>
|
||||||
|
<stylesheet>
|
||||||
|
<paraStyle name="main_footer" fontName="DejaVu Sans" fontSize="8.0" alignment="CENTER"/>
|
||||||
|
<paraStyle name="main_header" fontName="DejaVu Sans" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
||||||
|
</stylesheet>
|
||||||
|
<pageGraphics>
|
||||||
|
<drawString x="1.3cm" y="27.3cm">[[ company.partner_id.name ]]</drawString>
|
||||||
|
<place x="1.3cm" y="25.3cm" height="1.8cm" width="15.0cm">
|
||||||
|
<para style="main_header">[[ display_address(company.partner_id) or '' ]]</para>
|
||||||
|
</place>
|
||||||
|
</pageGraphics>
|
||||||
|
</pageTemplate>
|
||||||
|
</header>"""
|
||||||
|
|
||||||
|
_header_a4 = _header_main_wo_placeholders
|
BIN
bahmni-addons/bahmni_atom_feed/models/res_company.pyc
Normal file
BIN
bahmni-addons/bahmni_atom_feed/models/res_company.pyc
Normal file
Binary file not shown.
33
bahmni-addons/bahmni_atom_feed/models/res_users.py
Normal file
33
bahmni-addons/bahmni_atom_feed/models/res_users.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import odoo
|
||||||
|
from odoo import models, fields, api
|
||||||
|
|
||||||
|
|
||||||
|
class ResUsers(models.Model):
|
||||||
|
_inherit = 'res.users'
|
||||||
|
|
||||||
|
password = fields.Char(invisible=True)
|
||||||
|
shop_id = fields.Many2one('sale.shop', 'Shop')
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def check_credentials(self, password):
|
||||||
|
# convert to base_crypt if needed
|
||||||
|
self.env.cr.execute('SELECT password, password_crypt FROM res_users WHERE id=%s AND active', (self.env.uid,))
|
||||||
|
encrypted = None
|
||||||
|
user = self.env.user
|
||||||
|
if self.env.cr.rowcount:
|
||||||
|
stored, encrypted = self.env.cr.fetchone()
|
||||||
|
if stored and not encrypted:
|
||||||
|
user._set_password(stored)
|
||||||
|
self.invalidate_cache()
|
||||||
|
try:
|
||||||
|
return super(ResUsers, self).check_credentials(password)
|
||||||
|
except odoo.exceptions.AccessDenied:
|
||||||
|
if encrypted:
|
||||||
|
valid_pass, replacement = user._crypt_context()\
|
||||||
|
.verify_and_update(password, encrypted)
|
||||||
|
if replacement is not None:
|
||||||
|
user._set_encrypted_password(replacement)
|
||||||
|
if valid_pass:
|
||||||
|
return
|
||||||
|
raise
|
BIN
bahmni-addons/bahmni_atom_feed/models/res_users.pyc
Normal file
BIN
bahmni-addons/bahmni_atom_feed/models/res_users.pyc
Normal file
Binary file not shown.
@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from odoo import models, fields
|
||||||
|
|
||||||
|
|
||||||
|
class SyncableUnitsMapping(models.Model):
|
||||||
|
_name = 'syncable.units.mapping'
|
||||||
|
_description = "Units allowed to Sync mapped to Odoo Unit of Measures"
|
||||||
|
|
||||||
|
name = fields.Char(string="Unit Name", required=True)
|
||||||
|
unit_of_measure = fields.Many2one('product.uom', string="Unit of measure")
|
BIN
bahmni-addons/bahmni_atom_feed/models/syncable_units_mapping.pyc
Normal file
BIN
bahmni-addons/bahmni_atom_feed/models/syncable_units_mapping.pyc
Normal file
Binary file not shown.
@ -0,0 +1,9 @@
|
|||||||
|
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||||
|
access_order_type_shop_map,access_order_type_shop_map,model_order_type_shop_map,,1,1,1,1
|
||||||
|
access_event_records,access_event_records,model_event_records,,1,1,1,1
|
||||||
|
access_order_type,access_order_type,model_order_type,,1,1,1,1
|
||||||
|
access_atom_feed_marker,access_atom_feed_marker,model_atom_feed_marker,,1,1,1,1
|
||||||
|
access_syncable_units_mapping,access_syncable_units_mapping,model_syncable_units_mapping,,1,1,1,1
|
||||||
|
access_order_save_service,access_order_save_service,model_order_save_service,,1,1,1,1
|
||||||
|
access_atom_event_worker,access_atom_event_worker,model_atom_event_worker,base.group_user,,1,1,1,1
|
||||||
|
access_product_uom_service,access_product_uom_service,model_product_uom_service,,1,1,1,1
|
|
BIN
bahmni-addons/bahmni_atom_feed/static/description/icon.png
Normal file
BIN
bahmni-addons/bahmni_atom_feed/static/description/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
29
bahmni-addons/bahmni_atom_feed/views/event_records_view.xml
Normal file
29
bahmni-addons/bahmni_atom_feed/views/event_records_view.xml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="tree_event_records" model="ir.ui.view">
|
||||||
|
<field name="name">tree.event.records</field>
|
||||||
|
<field name="model">event.records</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<tree create="0" edit="0">
|
||||||
|
<field name="uuid"/>
|
||||||
|
<field name="title"/>
|
||||||
|
<field name="timestamp"/>
|
||||||
|
<field name="category"/>
|
||||||
|
<field name="uri"/>
|
||||||
|
<field name="object"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_event_records" model="ir.actions.act_window">
|
||||||
|
<field name="name">Event Records</field>
|
||||||
|
<field name="res_model">event.records</field>
|
||||||
|
<field name="view_type">form</field>
|
||||||
|
<field name="view_mode">tree</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem id="menu_event_records" action="action_event_records"
|
||||||
|
parent="base.menu_custom" />
|
||||||
|
|
||||||
|
</odoo>
|
@ -0,0 +1,72 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="tree_order_type" model="ir.ui.view">
|
||||||
|
<field name="name">tree.order.type</field>
|
||||||
|
<field name="model">order.type</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<tree string="Order Type">
|
||||||
|
<field name="name"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="form_order_type" model="ir.ui.view">
|
||||||
|
<field name="name">form.order.type</field>
|
||||||
|
<field name="model">order.type</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Order Type">
|
||||||
|
<group>
|
||||||
|
<field name="name" required="1"/>
|
||||||
|
</group>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_order_type" model="ir.actions.act_window">
|
||||||
|
<field name="name">Order Types</field>
|
||||||
|
<field name="res_model">order.type</field>
|
||||||
|
<field name="view_type">form</field>
|
||||||
|
<field name="view_mode">tree,form</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem id="menu_order_type"
|
||||||
|
action="action_order_type"
|
||||||
|
parent="sales_team.menu_sale_config"/>
|
||||||
|
|
||||||
|
<record id="tree_order_picking_type_mapping" model="ir.ui.view">
|
||||||
|
<field name="name">tree.order.picking.type.mapping</field>
|
||||||
|
<field name="model">order.picking.type.mapping</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<tree string="Order Picking Type Mapping" >
|
||||||
|
<field name="order_type_id" />
|
||||||
|
<field name="picking_type_id" />
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="form_order_picking_type_mapping" model="ir.ui.view">
|
||||||
|
<field name="name">form.order.picking.type.mapping</field>
|
||||||
|
<field name="model">order.picking.type.mapping</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Order Picking Type Mapping">
|
||||||
|
<group>
|
||||||
|
<field name="order_type_id" required="1"/>
|
||||||
|
<field name="picking_type_id" required="1"/>
|
||||||
|
</group>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_order_picking_type_mapping" model="ir.actions.act_window">
|
||||||
|
<field name="name">Order Type - Picking Type Mapping</field>
|
||||||
|
<field name="res_model">order.picking.type.mapping</field>
|
||||||
|
<field name="view_type">form</field>
|
||||||
|
<field name="view_mode">tree,form</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem id="menu_order_picking_type_mapping"
|
||||||
|
action="action_order_picking_type_mapping"
|
||||||
|
parent="sales_team.menu_sale_config"/>
|
||||||
|
|
||||||
|
</odoo>
|
@ -0,0 +1,61 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="order_type_shop_map_form_view" model="ir.ui.view">
|
||||||
|
<field name="name">Order Type Shop Map</field>
|
||||||
|
<field name="type">form</field>
|
||||||
|
<field name="model">order.type.shop.map</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Order Type Shop Map">
|
||||||
|
<sheet>
|
||||||
|
<group>
|
||||||
|
<field name="order_type"/>
|
||||||
|
<field name="shop_id"/>
|
||||||
|
<field name="location_id"/>
|
||||||
|
<field name="location_name"/>
|
||||||
|
</group>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="order_type_shop_map_tree_view" model="ir.ui.view">
|
||||||
|
<field name="name">order.type.shop.map.tree</field>
|
||||||
|
<field name="model">order.type.shop.map</field>
|
||||||
|
<field name="type">tree</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<tree string="Order Type Shop">
|
||||||
|
<field name="order_type"/>
|
||||||
|
<field name="shop_id"/>
|
||||||
|
<field name="location_id"/>
|
||||||
|
<field name="location_name"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="order_type_shop_map_filter" model="ir.ui.view">
|
||||||
|
<field name="name">order.type.shop.map.filter</field>
|
||||||
|
<field name="model">order.type.shop.map</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<search string="Search Order Type Shop">
|
||||||
|
<field name="order_type"/>
|
||||||
|
<field name="shop_id"/>
|
||||||
|
<field name="location_id"/>
|
||||||
|
<field name="location_name"/>
|
||||||
|
</search>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_order_type_shop_mapping" model="ir.actions.act_window">
|
||||||
|
<field name="name">Order Type - Shop mapping</field>
|
||||||
|
<field name="type">ir.actions.act_window</field>
|
||||||
|
<field name="res_model">order.type.shop.map</field>
|
||||||
|
<field name="view_type">form</field>
|
||||||
|
<field name="view_mode">tree,form</field>
|
||||||
|
<field name="context">{}</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem action="action_order_type_shop_mapping" id="menu_action_order_type_shop_mapping" parent="sales_team.menu_sale_config" sequence="34"/>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
|
|
36
bahmni-addons/bahmni_atom_feed/views/order_type_view.xml
Normal file
36
bahmni-addons/bahmni_atom_feed/views/order_type_view.xml
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="tree_order_type" model="ir.ui.view">
|
||||||
|
<field name="name">tree.order.type</field>
|
||||||
|
<field name="model">order.type</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<tree string="Order Type">
|
||||||
|
<field name="name"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="form_order_type" model="ir.ui.view">
|
||||||
|
<field name="name">form.order.type</field>
|
||||||
|
<field name="model">order.type</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Order Type">
|
||||||
|
<group>
|
||||||
|
<field name="name" required="1"/>
|
||||||
|
</group>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_order_type" model="ir.actions.act_window">
|
||||||
|
<field name="name">Order Types</field>
|
||||||
|
<field name="res_model">order.type</field>
|
||||||
|
<field name="view_type">form</field>
|
||||||
|
<field name="view_mode">tree,form</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem id="menu_order_type"
|
||||||
|
action="action_order_type"
|
||||||
|
parent="sales_team.menu_sale_config"/>
|
||||||
|
</odoo>
|
19
bahmni-addons/bahmni_atom_feed/views/res_company.xml
Normal file
19
bahmni-addons/bahmni_atom_feed/views/res_company.xml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<openerp>
|
||||||
|
<data>
|
||||||
|
<!--conutry_id is made required,
|
||||||
|
in case while creating patient, if country_id is not provided in address,
|
||||||
|
company's country_id will get set in patient address-->
|
||||||
|
<record id="res_company_country_required" model="ir.ui.view">
|
||||||
|
<field name="name">res.company.country.required</field>
|
||||||
|
<field name="model">res.company</field>
|
||||||
|
<field name="inherit_id" ref="base.view_company_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//field[@name='country_id']" position="attributes">
|
||||||
|
<attribute name="required">1</attribute>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</openerp>
|
14
bahmni-addons/bahmni_atom_feed/views/res_users_view.xml
Normal file
14
bahmni-addons/bahmni_atom_feed/views/res_users_view.xml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<odoo>
|
||||||
|
<record id="inherit_view_users_form_shop" model="ir.ui.view">
|
||||||
|
<field name="name">inherit.res.users.form.shop</field>
|
||||||
|
<field name="model">res.users</field>
|
||||||
|
<field name="inherit_id" ref="base.view_users_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//field[@name='partner_id']" position="after">
|
||||||
|
<field name="shop_id"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
@ -0,0 +1,38 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="form_syncable_units_mapping" model="ir.ui.view">
|
||||||
|
<field name="name">form.syncable.units.mapping</field>
|
||||||
|
<field name="model">syncable.units.mapping</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Syncable Units">
|
||||||
|
<group>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="unit_of_measure" />
|
||||||
|
</group>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="tree_syncable_units_mapping" model="ir.ui.view">
|
||||||
|
<field name="name">tree.syncable.units.mapping</field>
|
||||||
|
<field name="model">syncable.units.mapping</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<tree string="Syncable Units">
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="unit_of_measure" />
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_syncable_units_mapping" model="ir.actions.act_window">
|
||||||
|
<field name="name">Syncable Units Mapping</field>
|
||||||
|
<field name="res_model">syncable.units.mapping</field>
|
||||||
|
<field name="view_type">form</field>
|
||||||
|
<field name="view_mode">tree,form</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem id="menu_syncable_units_mapping" action="action_syncable_units_mapping"
|
||||||
|
parent="sales_team.menu_sale_config"/>
|
||||||
|
|
||||||
|
</odoo>
|
2
bahmni-addons/bahmni_atom_feed/wizard/__init__.py
Normal file
2
bahmni-addons/bahmni_atom_feed/wizard/__init__.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import stock_location_product_dhis2
|
BIN
bahmni-addons/bahmni_atom_feed/wizard/__init__.pyc
Normal file
BIN
bahmni-addons/bahmni_atom_feed/wizard/__init__.pyc
Normal file
Binary file not shown.
@ -0,0 +1,116 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import base64
|
||||||
|
import datetime
|
||||||
|
from dateutil.relativedelta import relativedelta
|
||||||
|
|
||||||
|
from odoo import models, fields, api
|
||||||
|
from odoo.addons.web.controllers.main import CSVExport
|
||||||
|
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT as DF, DEFAULT_SERVER_DATETIME_FORMAT as DTF
|
||||||
|
|
||||||
|
|
||||||
|
class StockLocationProductDhis2(models.TransientModel):
|
||||||
|
_name = 'stock.location.product.dhis2'
|
||||||
|
_description = "DHIS2 Export product stock at location"
|
||||||
|
|
||||||
|
MONTHS = [(1, "Jan"), (2, "Feb"), (3, "Mar"), (4, "Apr"), (5, "May"),
|
||||||
|
(6, "Jun"), (7, "Jul"),
|
||||||
|
(8, "Aug"), (9, "Sep"), (10, "Oct"), (11, "Nov"), (12, "Dec")]
|
||||||
|
|
||||||
|
FIELDS = ['dhis2_code', 'virtual_available']
|
||||||
|
HEADERS = ['dataelement', 'period', 'orgunit', 'categoryoptioncombo',
|
||||||
|
'value', 'storedby', 'lastupdated', 'comment', 'followup']
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _get_available_years_in_system(self):
|
||||||
|
'''this method will return selection values for the years,
|
||||||
|
for which entries are available in the system'''
|
||||||
|
res = []
|
||||||
|
date_today = datetime.date.today()
|
||||||
|
start_date_stock_move = self.env['stock.move'].search([], order='date asc', limit=1).date
|
||||||
|
if start_date_stock_move:
|
||||||
|
start_date_stock_move = datetime.datetime.strptime(start_date_stock_move, DTF).date()
|
||||||
|
start_date_account_move = self.env['account.move'].search([], order='date asc', limit=1).date
|
||||||
|
if start_date_account_move:
|
||||||
|
start_date_account_move = datetime.datetime.strptime(start_date_account_move, DF).date()
|
||||||
|
if start_date_account_move and start_date_stock_move:
|
||||||
|
start_year = False
|
||||||
|
if start_date_stock_move < start_date_account_move:
|
||||||
|
start_year = start_date_stock_move.year
|
||||||
|
else:
|
||||||
|
start_year = start_date_account_move.year
|
||||||
|
if (str(start_year), start_year) not in res:
|
||||||
|
res.append((str(start_year), start_year))
|
||||||
|
if date_today.year > start_year:
|
||||||
|
for i in range(start_year, date_today.year):
|
||||||
|
if (str(i+1), i+1) not in res:
|
||||||
|
res.append((str(i+1), i+1))
|
||||||
|
else:
|
||||||
|
if start_date_account_move:
|
||||||
|
res.append((str(start_date_account_move.year), start_date_account_move.year))
|
||||||
|
elif start_date_stock_move:
|
||||||
|
res.append((str(start_date_stock_move.year), start_date_stock_move.year))
|
||||||
|
else:
|
||||||
|
res.append((str(date_today.year), date_today.year))
|
||||||
|
return res
|
||||||
|
|
||||||
|
from_date = fields.Datetime(string="From Date")
|
||||||
|
to_date = fields.Datetime(string="To Date")
|
||||||
|
month = fields.Selection(MONTHS, string="Month")
|
||||||
|
year = fields.Selection(_get_available_years_in_system, string='Year')
|
||||||
|
data = fields.Binary(string="CSV file")
|
||||||
|
data_fname = fields.Char(string="File Name", default="stock_product_location.csv")
|
||||||
|
state = fields.Selection([('choose', 'choose'), ('get', 'get')],
|
||||||
|
string="State", default="choose")
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def action_generate_csv(self):
|
||||||
|
self.ensure_one()
|
||||||
|
dialog_box_data = self.read(['month', 'year', 'to_date', 'from_date'])[0]
|
||||||
|
export_data = self._get_export_data(dialog_box_data)
|
||||||
|
csv_data = CSVExport().from_data(self.HEADERS, export_data)
|
||||||
|
|
||||||
|
self.write({'data': base64.encodestring(csv_data),
|
||||||
|
'state': 'get'})
|
||||||
|
return {
|
||||||
|
'type': 'ir.actions.act_window',
|
||||||
|
'res_model': 'stock.location.product.dhis2',
|
||||||
|
'view_mode': 'form',
|
||||||
|
'view_type': 'form',
|
||||||
|
'res_id': self.id,
|
||||||
|
'views': [(False, 'form')],
|
||||||
|
'target': 'new',
|
||||||
|
}
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _get_export_data(self, dialog_box_data):
|
||||||
|
product_model = self.env['product.product']
|
||||||
|
domain = [('type', '<>', 'service')]
|
||||||
|
product_search_context = self.with_context(self._context)._create_product_search_context(dialog_box_data)
|
||||||
|
product_obj = product_model.with_context(product_search_context).search(domain)
|
||||||
|
|
||||||
|
export_data = product_obj.export_data(self.FIELDS).get('datas', [])
|
||||||
|
company = self.env['res.company'].browse(self._context.get('active_id'))
|
||||||
|
orgunit = company.dhis2_code
|
||||||
|
period = datetime.date(year=int(dialog_box_data['year']), month=int(dialog_box_data['month']), day=1).strftime("%Y%m")
|
||||||
|
modified_export_data = []
|
||||||
|
for row in export_data:
|
||||||
|
modified_row = []
|
||||||
|
modified_row.append(row[0]) #dataelement
|
||||||
|
modified_row.append(period)
|
||||||
|
modified_row.append(orgunit)
|
||||||
|
modified_row.append(None) #categoryoptioncombo
|
||||||
|
modified_row.append(row[1]) #value
|
||||||
|
modified_row.append(None) #storedby
|
||||||
|
modified_row.append(None) #lastupdated
|
||||||
|
modified_row.append(None) #comment
|
||||||
|
modified_row.append(None) #followup
|
||||||
|
modified_export_data.append(modified_row)
|
||||||
|
return modified_export_data
|
||||||
|
|
||||||
|
def _create_product_search_context(self, data):
|
||||||
|
first_day_of_month = datetime.date(year=int(data['year']), month=int(data['month']), day=1)
|
||||||
|
first_day_of_next_month = first_day_of_month + relativedelta(months=1)
|
||||||
|
return {
|
||||||
|
'from_date': data['from_date'],
|
||||||
|
'to_date': datetime.datetime.strftime(first_day_of_next_month, DF),
|
||||||
|
}
|
Binary file not shown.
@ -0,0 +1,45 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="form_stock_location_product_dhis2" model="ir.ui.view">
|
||||||
|
<field name="name">form.stock.location.product.dhis2</field>
|
||||||
|
<field name="model">stock.location.product.dhis2</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="View Stock of Products">
|
||||||
|
<field name="state" invisible="1"/>
|
||||||
|
|
||||||
|
<group>
|
||||||
|
<group>
|
||||||
|
<field name="month" required="1"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="year" required="1"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
<div states="get">
|
||||||
|
<h2>Click Link below to download file</h2>
|
||||||
|
<field name="data_fname" invisible="1"/>
|
||||||
|
<field name="data" readonly="1" filename="data_fname"/>
|
||||||
|
</div>
|
||||||
|
<footer states="choose">
|
||||||
|
<button name="action_generate_csv" string="Generate CSV"
|
||||||
|
type="object" class="oe_highlight" />
|
||||||
|
or
|
||||||
|
<button string="Cancel" class="oe_link" special="cancel"/>
|
||||||
|
</footer>
|
||||||
|
<footer states="get">
|
||||||
|
<button string="Close" class="oe_link" special="cancel" />
|
||||||
|
</footer>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<act_window name="Export To DHIS2"
|
||||||
|
res_model="stock.location.product.dhis2"
|
||||||
|
src_model="res.company"
|
||||||
|
view_mode="form"
|
||||||
|
target="new"
|
||||||
|
key2="client_action_multi"
|
||||||
|
id="action_bahmni_dhis2_code_export"/>
|
||||||
|
|
||||||
|
</odoo>
|
2
bahmni-addons/bahmni_product/__init__.py
Normal file
2
bahmni-addons/bahmni_product/__init__.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import models
|
24
bahmni-addons/bahmni_product/__manifest__.py
Normal file
24
bahmni-addons/bahmni_product/__manifest__.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
{
|
||||||
|
'name': 'Bahmni Product',
|
||||||
|
'version': '1.0',
|
||||||
|
'summary': 'Custom product module to meet bahmni requirement',
|
||||||
|
'sequence': 1,
|
||||||
|
'description': """
|
||||||
|
Bahmni Product
|
||||||
|
====================
|
||||||
|
""",
|
||||||
|
'category': 'Product',
|
||||||
|
'website': '',
|
||||||
|
'images': [],
|
||||||
|
'depends': ['product', 'product_expiry'],
|
||||||
|
'data': ['data/product_category.xml',
|
||||||
|
'views/res_partner_view.xml',
|
||||||
|
'views/product_supplierinfo_view.xml',
|
||||||
|
'views/product_view.xml'],
|
||||||
|
'demo': [],
|
||||||
|
'qweb': [],
|
||||||
|
'installable': True,
|
||||||
|
'application': True,
|
||||||
|
'auto_install': False,
|
||||||
|
}
|
33
bahmni-addons/bahmni_product/data/product_category.xml
Normal file
33
bahmni-addons/bahmni_product/data/product_category.xml
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
<record id="categ_saleable_drugs" model="product.category">
|
||||||
|
<field name="name">Drugs</field>
|
||||||
|
<field name="parent_id" ref="product.product_category_1"/>
|
||||||
|
</record>
|
||||||
|
<record id="categ_saleable_others" model="product.category">
|
||||||
|
<field name="name">Others</field>
|
||||||
|
<field name="parent_id" ref="product.product_category_1"/>
|
||||||
|
</record>
|
||||||
|
<record id="categ_all_services" model="product.category">
|
||||||
|
<field name="name">Services</field>
|
||||||
|
<field name="parent_id" ref="product.product_category_all"/>
|
||||||
|
</record>
|
||||||
|
<record id="categ_services_lab" model="product.category">
|
||||||
|
<field name="name">Lab</field>
|
||||||
|
<field name="parent_id" ref="bahmni_product.categ_all_services"/>
|
||||||
|
</record>
|
||||||
|
<record id="categ_services_lab_panel" model="product.category">
|
||||||
|
<field name="name">Panel</field>
|
||||||
|
<field name="parent_id" ref="bahmni_product.categ_services_lab"/>
|
||||||
|
</record>
|
||||||
|
<record id="categ_services_lab_test" model="product.category">
|
||||||
|
<field name="name">Test</field>
|
||||||
|
<field name="parent_id" ref="bahmni_product.categ_services_lab"/>
|
||||||
|
</record>
|
||||||
|
<record id="categ_services_radiology" model="product.category">
|
||||||
|
<field name="name">Radiology</field>
|
||||||
|
<field name="parent_id" ref="bahmni_product.categ_all_services"/>
|
||||||
|
</record>
|
||||||
|
</data>
|
||||||
|
</odoo>
|
6
bahmni-addons/bahmni_product/models/__init__.py
Normal file
6
bahmni-addons/bahmni_product/models/__init__.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import product_category
|
||||||
|
import product_uom
|
||||||
|
import res_partner
|
||||||
|
import product_supplierinfo
|
||||||
|
import product
|
BIN
bahmni-addons/bahmni_product/models/__init__.pyc
Normal file
BIN
bahmni-addons/bahmni_product/models/__init__.pyc
Normal file
Binary file not shown.
212
bahmni-addons/bahmni_product/models/product.py
Normal file
212
bahmni-addons/bahmni_product/models/product.py
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from copy import copy
|
||||||
|
from datetime import datetime, date
|
||||||
|
|
||||||
|
from odoo import models, fields, api
|
||||||
|
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT as DF
|
||||||
|
from odoo.tools.float_utils import float_round
|
||||||
|
|
||||||
|
|
||||||
|
class ProductProduct(models.Model):
|
||||||
|
_inherit = 'product.product'
|
||||||
|
|
||||||
|
@api.depends('stock_quant_ids', 'stock_move_ids')
|
||||||
|
def _compute_quantities(self):
|
||||||
|
res = self._compute_quantities_dict(self._context.get('lot_id'), self._context.get('owner_id'), self._context.get('package_id'), self._context.get('from_date'), self._context.get('to_date'))
|
||||||
|
for product in self:
|
||||||
|
product.qty_available = res[product.id]['qty_available']
|
||||||
|
product.incoming_qty = res[product.id]['incoming_qty']
|
||||||
|
product.outgoing_qty = res[product.id]['outgoing_qty']
|
||||||
|
product.virtual_available = res[product.id]['virtual_available']
|
||||||
|
product.actual_stock = res[product.id]['actual_stock']
|
||||||
|
|
||||||
|
def _compute_quantities_dict(self, lot_id, owner_id, package_id, from_date=False, to_date=False):
|
||||||
|
domain_quant_loc, domain_move_in_loc, domain_move_out_loc = self._get_domain_locations()
|
||||||
|
domain_quant = [('product_id', 'in', self.ids)] + domain_quant_loc
|
||||||
|
dates_in_the_past = False
|
||||||
|
if to_date and to_date < fields.Datetime.now(): #Only to_date as to_date will correspond to qty_available
|
||||||
|
dates_in_the_past = True
|
||||||
|
|
||||||
|
domain_move_in = [('product_id', 'in', self.ids)] + domain_move_in_loc
|
||||||
|
domain_move_out = [('product_id', 'in', self.ids)] + domain_move_out_loc
|
||||||
|
if lot_id:
|
||||||
|
domain_quant += [('lot_id', '=', lot_id)]
|
||||||
|
if owner_id:
|
||||||
|
domain_quant += [('owner_id', '=', owner_id)]
|
||||||
|
domain_move_in += [('restrict_partner_id', '=', owner_id)]
|
||||||
|
domain_move_out += [('restrict_partner_id', '=', owner_id)]
|
||||||
|
if package_id:
|
||||||
|
domain_quant += [('package_id', '=', package_id)]
|
||||||
|
# if dates_in_the_past:
|
||||||
|
domain_move_in_done = list(domain_move_out)
|
||||||
|
domain_move_out_done = list(domain_move_in)
|
||||||
|
domain_quant_actual_stock = copy(domain_quant)
|
||||||
|
if from_date:
|
||||||
|
domain_move_in += [('date', '>=', from_date)]
|
||||||
|
domain_move_out += [('date', '>=', from_date)]
|
||||||
|
if to_date:
|
||||||
|
domain_quant_actual_stock += ['|', '&', ('lot_id', '!=', False), '|', ('lot_id.life_date', '>=', to_date),
|
||||||
|
('lot_id.life_date', '=', False), ('lot_id', '=', False)]
|
||||||
|
domain_move_in += [('date', '<=', to_date)]
|
||||||
|
domain_move_out += [('date', '<=', to_date)]
|
||||||
|
else:
|
||||||
|
domain_quant_actual_stock += ['|', '&', ('lot_id', '!=', False), '|', ('lot_id.life_date', '>=', date.today().strftime(DF)),
|
||||||
|
('lot_id.life_date', '=', False), ('lot_id', '=', False)]
|
||||||
|
Move = self.env['stock.move']
|
||||||
|
Quant = self.env['stock.quant']
|
||||||
|
domain_move_in_todo = [('state', 'not in', ('done', 'cancel', 'draft'))] + domain_move_in
|
||||||
|
domain_move_out_todo = [('state', 'not in', ('done', 'cancel', 'draft'))] + domain_move_out
|
||||||
|
moves_in_res = dict((item['product_id'][0], item['product_qty']) for item in Move.read_group(domain_move_in_todo, ['product_id', 'product_qty'], ['product_id'], orderby='id'))
|
||||||
|
moves_out_res = dict((item['product_id'][0], item['product_qty']) for item in Move.read_group(domain_move_out_todo, ['product_id', 'product_qty'], ['product_id'], orderby='id'))
|
||||||
|
quants_res = dict((item['product_id'][0], item['qty']) for item in Quant.read_group(domain_quant, ['product_id', 'qty'], ['product_id'], orderby='id'))
|
||||||
|
quants_res_actual_stock = dict((item['product_id'][0], item['qty']) for item in Quant.read_group(domain_quant_actual_stock, ['product_id', 'qty'], ['product_id'], orderby='id'))
|
||||||
|
# if dates_in_the_past:
|
||||||
|
# Calculate the moves that were done before now to calculate back in time (as most questions will be recent ones)
|
||||||
|
if to_date:
|
||||||
|
domain_move_in_done = [('state', '=', 'done'), ('date', '>', to_date)] + domain_move_in_done
|
||||||
|
domain_move_out_done = [('state', '=', 'done'), ('date', '>', to_date)] + domain_move_out_done
|
||||||
|
else:
|
||||||
|
domain_move_in_done = [('state', '=', 'done')] + domain_move_in_done
|
||||||
|
domain_move_out_done = [('state', '=', 'done')] + domain_move_out_done
|
||||||
|
moves_in_res_past = dict((item['product_id'][0], item['product_qty']) for item in Move.read_group(domain_move_in_done, ['product_id', 'product_qty'], ['product_id'], orderby='id'))
|
||||||
|
moves_out_res_past = dict((item['product_id'][0], item['product_qty']) for item in Move.read_group(domain_move_out_done, ['product_id', 'product_qty'], ['product_id'], orderby='id'))
|
||||||
|
res = dict()
|
||||||
|
for product in self.with_context(prefetch_fields=False):
|
||||||
|
res[product.id] = {}
|
||||||
|
if dates_in_the_past:
|
||||||
|
actual_stock = quants_res_actual_stock.get(product.id, 0.0) - moves_in_res_past.get(product.id, 0.0) + moves_out_res_past.get(product.id, 0.0)
|
||||||
|
qty_available = quants_res.get(product.id, 0.0) - moves_in_res_past.get(product.id, 0.0) + moves_out_res_past.get(product.id, 0.0)
|
||||||
|
else:
|
||||||
|
qty_available = quants_res.get(product.id, 0.0)
|
||||||
|
actual_stock = quants_res_actual_stock.get(product.id, 0.0)
|
||||||
|
res[product.id]['qty_available'] = float_round(qty_available, precision_rounding=product.uom_id.rounding)
|
||||||
|
res[product.id]['actual_stock'] = float_round(actual_stock, precision_rounding=product.uom_id.rounding)
|
||||||
|
res[product.id]['incoming_qty'] = float_round(moves_in_res.get(product.id, 0.0), precision_rounding=product.uom_id.rounding)
|
||||||
|
res[product.id]['outgoing_qty'] = float_round(moves_out_res.get(product.id, 0.0), precision_rounding=product.uom_id.rounding)
|
||||||
|
res[product.id]['virtual_available'] = float_round(
|
||||||
|
qty_available + res[product.id]['incoming_qty'] - res[product.id]['outgoing_qty'],
|
||||||
|
precision_rounding=product.uom_id.rounding)
|
||||||
|
return res
|
||||||
|
|
||||||
|
stock_quant_ids = fields.One2many('stock.quant', 'product_id', help='Technical: used to compute quantities.')
|
||||||
|
stock_move_ids = fields.One2many('stock.move', 'product_id', help='Technical: used to compute quantities.')
|
||||||
|
actual_stock = fields.Integer(string="Actual Stock", compute=_compute_quantities,
|
||||||
|
help="Get the actual stock available for product."
|
||||||
|
"\nActual stock of product doesn't eliminates the count of expired lots from available quantities.")
|
||||||
|
mrp = fields.Float(string="MRP") # when variants exists for product, then mrp will be defined at variant level.
|
||||||
|
uuid = fields.Char(string="UUID")
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def create(self, vals):
|
||||||
|
if self._context.get('create_from_tmpl'):
|
||||||
|
if vals.get('product_tmpl_id') and vals.get('attribute_value_ids'):
|
||||||
|
if not vals.get('attribute_value_ids')[0][2]:
|
||||||
|
vals.update({'mrp': self.env['product.template'].browse(vals.get('product_tmpl_id')).mrp})
|
||||||
|
product = super(ProductProduct, self.with_context(create_product_product=True,
|
||||||
|
mrp=vals.get('mrp'))).create(vals)
|
||||||
|
# When a unique variant is created from tmpl then the standard price is set by _set_standard_price
|
||||||
|
if not (self.env.context.get('create_from_tmpl') and len(product.product_tmpl_id.product_variant_ids) == 1):
|
||||||
|
product._set_standard_price(vals.get('standard_price') or 0.0)
|
||||||
|
return product
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def write(self, vals):
|
||||||
|
res = super(ProductProduct, self).write(vals)
|
||||||
|
if vals.get('mrp') and not self._context.get('write_through_tmpl'):
|
||||||
|
if len(self.product_tmpl_id.product_variant_ids) == 1:
|
||||||
|
self.product_tmpl_id.mrp = vals.get('mrp')
|
||||||
|
return res
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def name_get(self):
|
||||||
|
'''inherited this method to add category name as suffix to product name
|
||||||
|
'''
|
||||||
|
res = super(ProductProduct, self).name_get()
|
||||||
|
result = []
|
||||||
|
for r in res:
|
||||||
|
categ_name = self.browse(r[0]).categ_id.name
|
||||||
|
result.append((r[0], r[1] + ' ('+categ_name+')'))
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class ProductTemplate(models.Model):
|
||||||
|
_inherit = 'product.template'
|
||||||
|
|
||||||
|
def _compute_quantities(self):
|
||||||
|
res = self._compute_quantities_dict()
|
||||||
|
for template in self:
|
||||||
|
template.qty_available = res[template.id]['qty_available']
|
||||||
|
template.virtual_available = res[template.id]['virtual_available']
|
||||||
|
template.incoming_qty = res[template.id]['incoming_qty']
|
||||||
|
template.outgoing_qty = res[template.id]['outgoing_qty']
|
||||||
|
template.actual_stock = res[template.id]['actual_stock']
|
||||||
|
|
||||||
|
def _compute_quantities_dict(self):
|
||||||
|
# TDE FIXME: why not using directly the function fields ?
|
||||||
|
variants_available = self.mapped('product_variant_ids')._product_available()
|
||||||
|
prod_available = {}
|
||||||
|
for template in self:
|
||||||
|
qty_available = 0
|
||||||
|
virtual_available = 0
|
||||||
|
incoming_qty = 0
|
||||||
|
outgoing_qty = 0
|
||||||
|
actual_stock = 0
|
||||||
|
for p in template.product_variant_ids:
|
||||||
|
qty_available += variants_available[p.id]["qty_available"]
|
||||||
|
virtual_available += variants_available[p.id]["virtual_available"]
|
||||||
|
incoming_qty += variants_available[p.id]["incoming_qty"]
|
||||||
|
outgoing_qty += variants_available[p.id]["outgoing_qty"]
|
||||||
|
actual_stock += variants_available[p.id]["actual_stock"]
|
||||||
|
prod_available[template.id] = {
|
||||||
|
"qty_available": qty_available,
|
||||||
|
"virtual_available": virtual_available,
|
||||||
|
"incoming_qty": incoming_qty,
|
||||||
|
"outgoing_qty": outgoing_qty,
|
||||||
|
"actual_stock": actual_stock,
|
||||||
|
}
|
||||||
|
return prod_available
|
||||||
|
|
||||||
|
uuid = fields.Char(string="UUID")
|
||||||
|
mrp = fields.Float(string="MRP")
|
||||||
|
manufacturer = fields.Many2one('res.partner', string="Manufacturer",
|
||||||
|
domain=[('manufacturer', '=', True)])
|
||||||
|
drug = fields.Char(string="Drug Name",
|
||||||
|
help="This field is for assigning Generic name to product")
|
||||||
|
|
||||||
|
actual_stock = fields.Integer(string="Actual Stock", compute=_compute_quantities,
|
||||||
|
help="Get the actual stock available for product."
|
||||||
|
"\nActual stock of product doesn't eliminates the count of expired lots from available quantities.")
|
||||||
|
dhis2_code = fields.Char(string="DHIS2 Code")
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def action_open_quants(self):
|
||||||
|
products = self.mapped('product_variant_ids')
|
||||||
|
action = self.env.ref('stock.product_open_quants').read()[0]
|
||||||
|
if self._context.get('show_actual_stock'):
|
||||||
|
action['domain'] = ['|', '&', ('lot_id', '!=', False), '|', ('lot_id.life_date', '>=', date.today().strftime(DF)),
|
||||||
|
('lot_id.life_date', '=', False), ('lot_id', '=', False)]
|
||||||
|
action['domain'] += [('product_id', 'in', products.ids)]
|
||||||
|
else:
|
||||||
|
action['domain'] = [('product_id', 'in', products.ids)]
|
||||||
|
action['context'] = {'search_default_locationgroup': 1, 'search_default_internal_loc': 1}
|
||||||
|
return action
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def create(self, vals):
|
||||||
|
# update mrp value in template, when template is getting created through product_product object
|
||||||
|
if self._context.get('create_product_product'):
|
||||||
|
vals.update({'mrp': self._context.get('mrp')})
|
||||||
|
return super(ProductTemplate, self).create(vals)
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def write(self, vals):
|
||||||
|
'''this method is inherited to set mrp price in product.product record
|
||||||
|
when changed in product.template, in case of no variants defined'''
|
||||||
|
self.ensure_one()
|
||||||
|
res = super(ProductTemplate, self).write(vals)
|
||||||
|
if vals.get('mrp'):
|
||||||
|
if len(self.product_variant_ids) == 1:
|
||||||
|
# context passed while calling write of product_product,
|
||||||
|
# as write method of product_product is also overridden to do same thing, hence to avoid recursion
|
||||||
|
self.product_variant_ids.with_context({'write_through_tmpl': True}).write({'mrp': vals.get('mrp')})
|
||||||
|
return res
|
BIN
bahmni-addons/bahmni_product/models/product.pyc
Normal file
BIN
bahmni-addons/bahmni_product/models/product.pyc
Normal file
Binary file not shown.
21
bahmni-addons/bahmni_product/models/product_category.py
Normal file
21
bahmni-addons/bahmni_product/models/product_category.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import uuid
|
||||||
|
from odoo import models, fields, api
|
||||||
|
|
||||||
|
|
||||||
|
class ProductCategory(models.Model):
|
||||||
|
_inherit = 'product.category'
|
||||||
|
|
||||||
|
uuid = fields.Char(string="UUID")
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def create(self, vals):
|
||||||
|
if vals.get('uuid') is None or not vals.get('uuid'):
|
||||||
|
vals.update({'uuid': uuid.uuid4()})
|
||||||
|
return super(ProductCategory, self).create(vals)
|
||||||
|
|
||||||
|
# need to override this method to reverse sync updated data
|
||||||
|
# @api.multi
|
||||||
|
# def write(self, vals):
|
||||||
|
# self.ensure_one()
|
||||||
|
# return super(ProductCategory, self).create(vals)
|
BIN
bahmni-addons/bahmni_product/models/product_category.pyc
Normal file
BIN
bahmni-addons/bahmni_product/models/product_category.pyc
Normal file
Binary file not shown.
10
bahmni-addons/bahmni_product/models/product_supplierinfo.py
Normal file
10
bahmni-addons/bahmni_product/models/product_supplierinfo.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from odoo import models, fields, api
|
||||||
|
|
||||||
|
|
||||||
|
class ProductSupplierinfo(models.Model):
|
||||||
|
_inherit = 'product.supplierinfo'
|
||||||
|
|
||||||
|
manufacturer = fields.Many2one('res.partner', string="Maufacturer",
|
||||||
|
domain=[('manufacturer', '=', True)])
|
||||||
|
mrp = fields.Float(string="MRP")
|
BIN
bahmni-addons/bahmni_product/models/product_supplierinfo.pyc
Normal file
BIN
bahmni-addons/bahmni_product/models/product_supplierinfo.pyc
Normal file
Binary file not shown.
39
bahmni-addons/bahmni_product/models/product_uom.py
Normal file
39
bahmni-addons/bahmni_product/models/product_uom.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from odoo import models, fields, api
|
||||||
|
|
||||||
|
|
||||||
|
class ProductUom(models.Model):
|
||||||
|
_inherit = 'product.uom'
|
||||||
|
|
||||||
|
uuid = fields.Char(string="UUID")
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def create(self, vals):
|
||||||
|
if vals.get('uuid') is None or not vals.get('uuid'):
|
||||||
|
vals.update({'uuid': uuid.uuid4()})
|
||||||
|
return super(ProductUom, self).create(vals)
|
||||||
|
|
||||||
|
# need to override this method to reverse sync updated data
|
||||||
|
@api.multi
|
||||||
|
def write(self, vals):
|
||||||
|
return super(ProductUom, self).write(vals)
|
||||||
|
|
||||||
|
|
||||||
|
class ProductUomCategory(models.Model):
|
||||||
|
_inherit = 'product.uom.categ'
|
||||||
|
|
||||||
|
uuid = fields.Char(string="UUID")
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def create(self, vals):
|
||||||
|
if vals.get('uuid') is None or not vals.get('uuid'):
|
||||||
|
vals.update({'uuid': uuid.uuid4()})
|
||||||
|
return super(ProductUomCategory, self).create(vals)
|
||||||
|
|
||||||
|
# need to override this method to reverse sync updated data
|
||||||
|
@api.multi
|
||||||
|
def write(self, vals):
|
||||||
|
self.ensure_one()
|
||||||
|
return super(ProductUomCategory, self).write(vals)
|
BIN
bahmni-addons/bahmni_product/models/product_uom.pyc
Normal file
BIN
bahmni-addons/bahmni_product/models/product_uom.pyc
Normal file
Binary file not shown.
8
bahmni-addons/bahmni_product/models/res_partner.py
Normal file
8
bahmni-addons/bahmni_product/models/res_partner.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from odoo import models, fields
|
||||||
|
|
||||||
|
|
||||||
|
class ResPartner(models.Model):
|
||||||
|
_inherit = 'res.partner'
|
||||||
|
|
||||||
|
manufacturer = fields.Boolean(string='Manufacturer') # boolean field to identify manufacturers
|
BIN
bahmni-addons/bahmni_product/models/res_partner.pyc
Normal file
BIN
bahmni-addons/bahmni_product/models/res_partner.pyc
Normal file
Binary file not shown.
BIN
bahmni-addons/bahmni_product/static/description/icon.png
Normal file
BIN
bahmni-addons/bahmni_product/static/description/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="manufacturer_product_suppliernfo" model="ir.ui.view">
|
||||||
|
<field name="name">manufacturer.product.supplierinfo</field>
|
||||||
|
<field name="model">product.supplierinfo</field>
|
||||||
|
<field name="inherit_id" ref="product.product_supplierinfo_form_view"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//group/group[1]/div" position="after">
|
||||||
|
<field name="manufacturer"
|
||||||
|
context="{'default_manufacturer': True, 'default_customer': False}"/>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//group/group[2]/div[2]" position="after">
|
||||||
|
<label for="mrp"/>
|
||||||
|
<div>
|
||||||
|
<field name="mrp" class="oe_inline"/>
|
||||||
|
</div>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user