view tryton/health_stock/health_stock.py @ 720:8c9e8bc46144

health_inpatient : Add wizard to setup.py
author Sebastian Marro <smarro@thymbra.com>
date Mon, 18 Mar 2013 01:52:22 -0300
parents fe4044398a9f
children 6d95de57b2b9
line wrap: on
line source
# -*- coding: utf-8 -*-
##############################################################################
#
#    GNU Health: The Free Health and Hospital Information System
#    Copyright (C) 2008-2013  Luis Falcon <lfalcon@gnusolidario.org>
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from datetime import datetime
from trytond.model import Workflow, ModelView, ModelSQL, fields
from trytond.wizard import Wizard, StateView, Button, StateTransition
from trytond.pyson import If, Or, Eval, Not, Bool
from trytond.exceptions import UserError
from trytond.transaction import Transaction
from trytond.pool import Pool, PoolMeta

__all__ = ['Medicament', 'Party', 'Lot', 'ShipmentOut', 'Move',
    'PatientAmbulatoryCare', 'PatientAmbulatoryCareMedicament',
    'PatientAmbulatoryCareMedicalSupply', 'PatientAmbulatoryCareVaccine',
    'PatientRounding', 'PatientRoundingMedicament',
    'PatientRoundingMedicalSupply', 'PatientRoundingVaccine',
    'CreatePrescriptionShipmentInit', 'CreatePrescriptionShipment']
__metaclass__ = PoolMeta

_STATES = {
    'readonly': Eval('state') == 'done',
    }
_DEPENDS = ['state']


class Medicament:
    __name__ = 'gnuhealth.medicament'
    quantity = fields.Function(fields.Float('Quantity'), 'get_quantity')

    def get_quantity(self, name):
        pool = Pool()
        Date = pool.get('ir.date')
        Location = pool.get('stock.location')
        Product = pool.get('product.product')

        locations = Location.search([('type', '=', 'storage')])
        Transaction().set_context({'locations': [l.id for l in locations]})
        context = {}
        context['stock_date_end'] = Date.today()
        Transaction().set_context(context)

        pbl = Product.products_by_location(
                location_ids=Transaction().context['locations'],
                product_ids=[self.name.id], with_childs=True)
        quantity = 0.00
        if pbl.values():
            quantity = reduce(lambda x, y: x + y, pbl.values())
        return quantity


class Party:
    __name__ = 'party.party'
    warehouse = fields.Many2One('stock.location', 'Warehouse',
        domain=[('type', '=', 'warehouse')],
        states={
            'invisible': Not(Bool(Eval('is_pharmacy'))),
            'required': Bool(Eval('is_pharmacy')),
            },
        depends=['is_pharmacy'])

    @classmethod
    def default_warehouse(cls):
        Location = Pool().get('stock.location')
        locations = Location.search(cls.warehouse.domain)
        if len(locations) == 1:
            return locations[0].id


class Lot:
    __name__ = 'stock.lot'
    expiration_date = fields.Date('Expiration Date')
    quantity = fields.Function(fields.Float('Quantity'), 'sum_lot_quantity')

    def sum_lot_quantity(self, name):
        Move = Pool().get('stock.move')

        moves = Move.search([
            ('lot', '=', self.id),
            ('product', '=', self.product.id)
        ])
        quantity = 0
        for move in moves:
            if move.to_location.type == 'storage':
                quantity += move.quantity
            if move.from_location.type == 'storage':
                quantity -= move.quantity
        return quantity


class ShipmentOut:
    __name__ = 'stock.shipment.out'
    prescription_order = fields.Many2One('gnuhealth.prescription.order',
        'Source Prescription')


class Move:
    __name__ = 'stock.move'
    ambulatory_care = fields.Many2One('gnuhealth.patient.ambulatory_care',
        'Source Ambulatory Care')
    rounding = fields.Many2One('gnuhealth.patient.rounding',
        'Source Rounding')


class PatientAmbulatoryCare(Workflow, ModelSQL, ModelView):
    'Patient Ambulatory Care'
    __name__ = 'gnuhealth.patient.ambulatory_care'

    care_location = fields.Many2One('stock.location', 'Care Location',
        states={
            'required': If(Or(Bool(Eval('medicaments')),
                Bool(Eval('medical_supplies')),
                Bool(Eval('vaccines'))), True, False),
            'readonly': Eval('state') == 'done',
        }, depends=['state', 'medicaments'])
    medicaments = fields.One2Many(
        'gnuhealth.patient.ambulatory_care.medicament', 'name',
        'Medicaments', states=_STATES, depends=_DEPENDS)
    medical_supplies = fields.One2Many(
        'gnuhealth.patient.ambulatory_care.medical_supply', 'name',
        'Medical Supplies', states=_STATES, depends=_DEPENDS)
    vaccines = fields.One2Many(
        'gnuhealth.patient.ambulatory_care.vaccine', 'name', 'Vaccines',
        states=_STATES, depends=_DEPENDS)
    moves = fields.One2Many('stock.move', 'ambulatory_care', 'Stock Moves',
        readonly=True)
    state = fields.Selection([
        ('draft', 'Draft'),
        ('done', 'Done'),
        ], 'State', readonly=True)

    @classmethod
    def __setup__(cls):
        super(PatientAmbulatoryCare, cls).__setup__()
        cls._transitions |= set((
            ('draft', 'done'),
        ))
        cls._buttons.update({
            'done': {
                'invisible': ~Eval('state').in_(['draft']),
            }})

    @staticmethod
    def default_state():
        return 'draft'

    @classmethod
    @ModelView.button
    @Workflow.transition('done')
    def done(cls, ambulatory_cares):
        pool = Pool()
        Patient = pool.get('gnuhealth.patient')
        Vaccination = pool.get('gnuhealth.vaccination')

        lines_to_ship = {}
        medicaments_to_ship = []
        supplies_to_ship = []
        vaccines_to_ship = []

        for ambulatory in ambulatory_cares:
            patient = Patient(ambulatory.patient.id)
            for medicament in ambulatory.medicaments:
                medicaments_to_ship.append(medicament)

            for medical_supply in ambulatory.medical_supplies:
                supplies_to_ship.append(medical_supply)

            for vaccine in ambulatory.vaccines:
                lot_number = ''
                expiration_date = ''
                if vaccine.lot:
                    if vaccine.lot.number:
                        lot_number = vaccine.lot.number
                    if vaccine.lot.expiration_date:
                        expiration_date = vaccine.lot.expiration_date
                vaccination_data = {
                    'name': patient.id,
                    'vaccine': vaccine.vaccine.id,
                    'vaccine_lot': lot_number,
                    'institution': Transaction().context.get('company')
                        or None,
                    'date': datetime.now(),
                    'dose': vaccine.dose,
                    'next_dose_date': vaccine.next_dose_date,
                    'vaccine_expiration_date': expiration_date
                    }
                Vaccination.create(vaccination_data)
                vaccines_to_ship.append(vaccine)

        lines_to_ship['medicaments'] = medicaments_to_ship
        lines_to_ship['supplies'] = supplies_to_ship
        lines_to_ship['vaccines'] = vaccines_to_ship

        cls.create_stock_moves(ambulatory_cares, lines_to_ship)

    @classmethod
    def create_stock_moves(cls, ambulatory_cares, lines):
        pool = Pool()
        Move = pool.get('stock.move')
        Date = pool.get('ir.date')
        for ambulatory in ambulatory_cares:
            for medicament in lines['medicaments']:
                move_info = {}
                move_info['product'] = medicament.medicament.name.id
                move_info['uom'] = medicament.medicament.name.default_uom.id
                move_info['quantity'] = medicament.quantity
                move_info['from_location'] = ambulatory.care_location.id
                move_info['to_location'] = \
                    ambulatory.patient.name.customer_location.id
                move_info['unit_price'] = 1
                move_info['ambulatory_care'] = ambulatory.id
                if medicament.lot:
                    if  medicament.lot.expiration_date \
                    and medicament.lot.expiration_date < Date.today():
                        raise UserError('Expired medicaments')
                    move_info['lot'] = medicament.lot.id

                new_move = Move.create(move_info)
                Move.write([new_move], {
                    'state': 'done',
                    'effective_date': Date.today(),
                })

            for medical_supply in lines['supplies']:
                move_info = {}
                move_info['product'] = medical_supply.product.id
                move_info['uom'] = medical_supply.product.default_uom.id
                move_info['quantity'] = medical_supply.quantity
                move_info['from_location'] = ambulatory.care_location.id
                move_info['to_location'] = \
                    ambulatory.patient.name.customer_location.id
                move_info['unit_price'] = 1
                move_info['ambulatory_care'] = ambulatory.id
                if medical_supply.lot:
                    if  medical_supply.lot.expiration_date \
                    and medical_supply.lot.expiration_date < Date.today():
                        raise UserError('Expired supplies')
                    move_info['lot'] = medical_supply.lot.id

                new_move = Move.create(move_info)
                Move.write([new_move], {
                    'state': 'done',
                    'effective_date': Date.today(),
                })

            for vaccine in lines['vaccines']:
                move_info = {}
                move_info['product'] = vaccine.vaccine.id
                move_info['uom'] = vaccine.vaccine.default_uom.id
                move_info['quantity'] = vaccine.quantity
                move_info['from_location'] = ambulatory.care_location.id
                move_info['to_location'] = \
                    ambulatory.patient.name.customer_location.id
                move_info['unit_price'] = 1
                move_info['ambulatory_care'] = ambulatory.id
                if vaccine.lot:
                    if  vaccine.lot.expiration_date \
                    and vaccine.lot.expiration_date < Date.today():
                        raise UserError('Expired vaccines')
                    move_info['lot'] = vaccine.lot.id
                new_move = Move.create(move_info)
                Move.write([new_move], {
                    'state': 'done',
                    'effective_date': Date.today(),
                })

        return True


class PatientAmbulatoryCareMedicament(ModelSQL, ModelView):
    'Patient Ambulatory Care Medicament'
    __name__ = 'gnuhealth.patient.ambulatory_care.medicament'

    name = fields.Many2One('gnuhealth.patient.ambulatory_care',
        'Ambulatory ID')
    medicament = fields.Many2One('gnuhealth.medicament', 'Medicament',
        required=True, on_change=['medicament', 'product'])
    product = fields.Many2One('product.product', 'Product')
    quantity = fields.Integer('Quantity')
    short_comment = fields.Char('Comment',
        help='Short comment on the specific drug')
    lot = fields.Many2One('stock.lot', 'Lot', depends=['product'],
        domain=[('product', '=', Eval('product'))])

    @staticmethod
    def default_quantity():
        return 1

    def on_change_medicament(self):
        res = {}
        if self.medicament:
            res = {'product': self.medicament.name.id}
        else:
            res = {'product': None}
        return res


class PatientAmbulatoryCareMedicalSupply(ModelSQL, ModelView):
    'Patient Ambulatory Care Medical Supply'
    __name__ = 'gnuhealth.patient.ambulatory_care.medical_supply'

    name = fields.Many2One('gnuhealth.patient.ambulatory_care',
        'Ambulatory ID')
    product = fields.Many2One('product.product', 'Medical Supply',
        domain=[('is_medical_supply', '=', True)], required=True)
    quantity = fields.Integer('Quantity')
    short_comment = fields.Char('Comment',
        help='Short comment on the specific drug')
    lot = fields.Many2One('stock.lot', 'Lot', depends=['product'],
        domain=[
            ('product', '=', Eval('product')),
            ])

    @staticmethod
    def default_quantity():
        return 1


class PatientAmbulatoryCareVaccine(ModelSQL, ModelView):
    'Patient Ambulatory Care Vaccine'
    __name__ = 'gnuhealth.patient.ambulatory_care.vaccine'

    name = fields.Many2One('gnuhealth.patient.ambulatory_care',
        'Ambulatory ID')
    vaccine = fields.Many2One('product.product', 'Name', required=True,
        domain=[('is_vaccine', '=', True)])
    quantity = fields.Integer('Quantity')
    dose = fields.Integer('Dose')
    next_dose_date = fields.DateTime('Next Dose')
    short_comment = fields.Char('Comment',
        help='Short comment on the specific drug')
    lot = fields.Many2One('stock.lot', 'Lot', depends=['vaccine'],
        domain=[
            ('product', '=', Eval('vaccine')),
            ])

    @staticmethod
    def default_quantity():
        return 1


class PatientRounding(Workflow, ModelSQL, ModelView):
    'Patient Ambulatory Care'
    __name__ = 'gnuhealth.patient.rounding'

    hospitalization_location = fields.Many2One('stock.location',
        'Hospitalization Location',
        states={
            'required': If(Or(Bool(Eval('medicaments')),
                Bool(Eval('medical_supplies')),
                Bool(Eval('vaccines'))), True, False),
            'readonly': Eval('state') == 'done',
        }, depends=_DEPENDS)
    medicaments = fields.One2Many(
        'gnuhealth.patient.rounding.medicament', 'name', 'Medicaments',
        states=_STATES, depends=_DEPENDS)
    medical_supplies = fields.One2Many(
        'gnuhealth.patient.rounding.medical_supply', 'name', 'Medical Supplies',
        states=_STATES, depends=_DEPENDS)
    vaccines = fields.One2Many(
        'gnuhealth.patient.rounding.vaccine', 'name', 'Vaccines',
        states=_STATES, depends=_DEPENDS,)
    moves = fields.One2Many('stock.move', 'rounding', 'Stock Moves',
        readonly=True)
    state = fields.Selection([
        ('draft', 'Draft'),
        ('done', 'Done'),
        ], 'State', readonly=True)

    @classmethod
    def __setup__(cls):
        super(PatientRounding, cls).__setup__()
        cls._transitions |= set((
        ('draft', 'done'),
        ))
        cls._buttons.update({
            'done': {
            'invisible': ~Eval('state').in_(['draft']),
            }})

    @staticmethod
    def default_state():
        return 'draft'

    @classmethod
    @ModelView.button
    @Workflow.transition('done')
    def done(cls, roundings):
        pool = Pool()
        Patient = pool.get('gnuhealth.patient')
        Vaccination = pool.get('gnuhealth.vaccination')

        lines_to_ship = {}
        medicaments_to_ship = []
        supplies_to_ship = []
        vaccines_to_ship = []

        for rounding in roundings:
            patient = Patient(rounding.name.patient.id)

            for medicament in rounding.medicaments:
                medicaments_to_ship.append(medicament)

            for medical_supply in rounding.medical_supplies:
                supplies_to_ship.append(medical_supply)

            for vaccine in rounding.vaccines:
                lot_number = ''
                expiration_date = ''
                if vaccine.lot:
                    if vaccine.lot.number:
                        lot_number = vaccine.lot.number
                    if vaccine.lot.expiration_date:
                        expiration_date = vaccine.lot.expiration_date
                vaccination_data = {
                    'name': patient.id,
                    'vaccine': vaccine.vaccine.id,
                    'vaccine_lot': lot_number,
                    'institution': Transaction().context.get('company')
                        or None,
                    'date': datetime.now(),
                    'dose': vaccine.dose,
                    'next_dose_date': vaccine.next_dose_date,
                    'vaccine_expiration_date': expiration_date
                    }
                Vaccination.create(vaccination_data)
                vaccines_to_ship.append(vaccine)


        lines_to_ship['medicaments'] = medicaments_to_ship
        lines_to_ship['supplies'] = supplies_to_ship
        lines_to_ship['vaccines'] = vaccines_to_ship

        cls.create_stock_moves(roundings, lines_to_ship)

    @classmethod
    def create_stock_moves(cls, roundings, lines):
        pool = Pool()
        Move = pool.get('stock.move')
        Date = pool.get('ir.date')
        for rounding in roundings:
            for medicament in lines['medicaments']:
                move_info = {}
                move_info['product'] = medicament.medicament.name.id
                move_info['uom'] = medicament.medicament.name.default_uom.id
                move_info['quantity'] = medicament.quantity
                move_info['from_location'] = \
                    rounding.hospitalization_location.id
                move_info['to_location'] = \
                rounding.name.patient.name.customer_location.id
                move_info['unit_price'] = 1
                move_info['rounding'] = rounding.id
                if medicament.lot:
                    if  medicament.lot.expiration_date \
                    and medicament.lot.expiration_date < Date.today():
                        raise UserError('Expired medicaments')
                    move_info['lot'] = medicament.lot.id
                new_move = Move.create(move_info)
                Move.write([new_move], {
                    'state': 'done',
                    'effective_date': Date.today(),
                    })
            for medical_supply in lines['supplies']:
                    move_info = {}
                    move_info['product'] = medical_supply.product.id
                    move_info['uom'] = medical_supply.product.default_uom.id
                    move_info['quantity'] = medical_supply.quantity
                    move_info['from_location'] = \
                        rounding.hospitalization_location.id
                    move_info['to_location'] = \
                    rounding.name.patient.name.customer_location.id
                    move_info['unit_price'] = 1
                    move_info['rounding'] = rounding.id
                    if medical_supply.lot:
                        if  medical_supply.lot.expiration_date \
                        and medical_supply.lot.expiration_date < Date.today():
                            raise UserError('Expired supplies')
                        move_info['lot'] = medical_supply.lot.id
                    new_move = Move.create(move_info)
                    Move.write([new_move], {
                                    'state': 'done',
                                    'effective_date': Date.today(),
                                })
            for vaccine in lines['vaccines']:
                move_info = {}
                move_info['product'] = vaccine.vaccine.id
                move_info['uom'] = vaccine.vaccine.default_uom.id
                move_info['quantity'] = vaccine.quantity
                move_info['from_location'] = \
                    rounding.hospitalization_location.id
                move_info['to_location'] = \
                rounding.name.patient.name.customer_location.id
                move_info['unit_price'] = 1
                move_info['rounding'] = rounding.id
                if vaccine.lot:
                    if  vaccine.lot.expiration_date \
                    and vaccine.lot.expiration_date < Date.today():
                        raise UserError('Expired vaccines')
                    move_info['lot'] = vaccine.lot.id
                new_move = Move.create(move_info)
                Move.write([new_move], {
                    'state': 'done',
                    'effective_date': Date.today(),
                })

        return True


class PatientRoundingMedicament(ModelSQL, ModelView):
    'Patient Rounding Medicament'
    __name__ = 'gnuhealth.patient.rounding.medicament'

    name = fields.Many2One('gnuhealth.patient.rounding', 'Ambulatory ID')
    medicament = fields.Many2One('gnuhealth.medicament', 'Medicament',
        required=True, on_change=['medicament', 'product'])
    product = fields.Many2One('product.product', 'Product')
    quantity = fields.Integer('Quantity')
    short_comment = fields.Char('Comment',
        help='Short comment on the specific drug')
    lot = fields.Many2One('stock.lot', 'Lot', depends=['product'],
        domain=[('product', '=', Eval('product'))])

    @staticmethod
    def default_quantity():
        return 1

    def on_change_medicament(self):
        res = {}
        if self.medicament:
            res = {'product': self.medicament.name.id}
        else:
            res = {'product': None}
        return res


class PatientRoundingMedicalSupply(ModelSQL, ModelView):
    'Patient Rounding Medical Supply'
    __name__ = 'gnuhealth.patient.rounding.medical_supply'

    name = fields.Many2One('gnuhealth.patient.rounding', 'Ambulatory ID')
    product = fields.Many2One('product.product', 'Medical Supply',
        domain=[('is_medical_supply', '=', True)], required=True)
    quantity = fields.Integer('Quantity')
    short_comment = fields.Char('Comment',
        help='Short comment on the specific drug')
    lot = fields.Many2One('stock.lot', 'Lot', depends=['product'],
        domain=[
            ('product', '=', Eval('product')),
            ])

    @staticmethod
    def default_quantity():
        return 1


class PatientRoundingVaccine(ModelSQL, ModelView):
    'Patient Ambulatory Care Vaccine'
    __name__ = 'gnuhealth.patient.rounding.vaccine'

    name = fields.Many2One('gnuhealth.patient.rounding', 'Rounding ID')
    vaccine = fields.Many2One('product.product', 'Name', required=True,
        domain=[('is_vaccine', '=', True)])
    quantity = fields.Integer('Quantity')
    dose = fields.Integer('Dose')
    next_dose_date = fields.DateTime('Next Dose')
    short_comment = fields.Char('Comment',
        help='Short comment on the specific drug')
    lot = fields.Many2One('stock.lot', 'Lot', depends=['vaccine'],
        domain=[
            ('product', '=', Eval('vaccine')),
            ])

    @staticmethod
    def default_quantity():
        return 1


class CreatePrescriptionShipmentInit(ModelView):
    'Create Prescription Shipment'
    __name__ = 'gnuhealth.prescription.shipment.init'


class CreatePrescriptionShipment(Wizard):
    'Create Prescription Shipment'
    __name__ = 'gnuhealth.prescription.shipment.create'

    start = StateView('gnuhealth.prescription.shipment.init',
            'health_stock.view_create_prescription_shipment', [
            Button('Cancel', 'end', 'tryton-cancel'),
            Button('Create Shipment', 'create_shipment',
                'tryton-ok', True),
            ])
    create_shipment = StateTransition()

    def transition_create_shipment(self):
        pool = Pool()
        Shipment = pool.get('stock.shipment.out')
        ShipmentLine = pool.get('stock.move')
        StockLocation = Pool().get('stock.location')
        Prescription = pool.get('gnuhealth.prescription.order')

        prescriptions = Prescription.browse(Transaction().context.get(
            'active_ids'))
        shipment_data = {}
        for prescription in prescriptions:
            shipment_data['customer'] = prescription.patient.name.id
            shipment_data['company'] = Transaction().context['company']
            shipment_data['warehouse'] = prescription.pharmacy.warehouse
            shipment_data['prescription_order'] = prescription.id
            if prescription.patient.name.addresses:
                shipment_data['delivery_address'] = \
                    prescription.patient.name.addresses[0]
            else:
                raise Exception('You need to define an address in the party \
                    form.')

            shipment = Shipment.create(shipment_data)

            #Create Shipment Lines
            warehouse = StockLocation(prescription.pharmacy.warehouse)
            Shipment.wait([shipment])
            for line in prescription.prescription_line:
                shipment_line_data = {}
                shipment_line_data['shipment_out'] = shipment.id
                shipment_line_data['from_location'] = \
                    warehouse.storage_location.id
                shipment_line_data['to_location'] = \
                    warehouse.output_location.id
                shipment_line_data['product'] = \
                    line.template.medicament.name.id
                shipment_line_data['quantity'] = line.quantity
                shipment_line_data['uom'] = \
                    line.template.medicament.name.default_uom.id
                shipment_line_data['state'] = 'assigned'
                ShipmentLine.create(shipment_line_data)

        return 'end'