This commit is contained in:
travelershot 2024-12-12 22:37:39 +06:00
commit 70dda814aa
2270 changed files with 912039 additions and 0 deletions

15
bahmni-addons/LICENSE Normal file
View 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
View 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
View File

@ -0,0 +1,2 @@
# odoo-modules
Custom Odoo modules (extensions) for Bahmni

View File

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
import models
import report

View 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,
}

View 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>

View 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.

View 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.

View 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

Binary file not shown.

View File

@ -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)

View 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

View 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)

View 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")

View 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")

Binary file not shown.

View 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")

View 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

Binary file not shown.

View File

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
import account_count_report
import account_report

Binary file not shown.

View 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!')

View 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', '&lt;=', context_today().strftime('%%Y-%%m-%%d')],
['date', '&gt;=', (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>

View 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)
)""")

Binary file not shown.

View 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', '&lt;=', context_today().strftime('%%Y-%%m-%%d')],
['date', '&gt;=', (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>

View 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>

View File

@ -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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_account_count_report_account_user Account Count Report Account User model_account_count_report account.group_account_user 1 1 1 0
3 access_rounding_off access_rounding_off model_rounding_off 1 1 1 1
4 access_account_report access_account_report model_account_report 1 1 1 1

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -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;
}

View File

@ -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>

View 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>

View 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>

View 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>

View File

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
import models
import wizard

View 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,
}

View File

@ -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>

View 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

Binary file not shown.

View 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)

View 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)

View 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

View 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)

View File

@ -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

View 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()

View 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!')]

Binary file not shown.

View 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

View 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)

View File

@ -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})

View 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

Binary file not shown.

View 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

Binary file not shown.

View File

@ -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")

View File

@ -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
1 id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
2 access_order_type_shop_map,access_order_type_shop_map,model_order_type_shop_map,,1,1,1,1
3 access_event_records,access_event_records,model_event_records,,1,1,1,1
4 access_order_type,access_order_type,model_order_type,,1,1,1,1
5 access_atom_feed_marker,access_atom_feed_marker,model_atom_feed_marker,,1,1,1,1
6 access_syncable_units_mapping,access_syncable_units_mapping,model_syncable_units_mapping,,1,1,1,1
7 access_order_save_service,access_order_save_service,model_order_save_service,,1,1,1,1
8 access_atom_event_worker,access_atom_event_worker,model_atom_event_worker,base.group_user,,1,1,1,1
9 access_product_uom_service,access_product_uom_service,model_product_uom_service,,1,1,1,1

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View 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>

View File

@ -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>

View File

@ -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>

View 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>

View 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>

View 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>

View File

@ -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>

View File

@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
import stock_location_product_dhis2

Binary file not shown.

View File

@ -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),
}

View File

@ -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>

View File

@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
import models

View 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,
}

View 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>

View File

@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
import product_category
import product_uom
import res_partner
import product_supplierinfo
import product

Binary file not shown.

View 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

Binary file not shown.

View 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)

View 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")

View 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)

Binary file not shown.

View 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

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -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