From c5c7b6ec366b5d832045d08ebcd02b7aadd42836 Mon Sep 17 00:00:00 2001 From: Bertrand Chenal Date: Tue, 8 Apr 2008 14:27:53 +0200 Subject: [PATCH] Stock. initial commit --- __init__.py | 31 +++++++ __tryton__.py | 19 +++++ location.py | 48 +++++++++++ location.xml | 65 +++++++++++++++ lot.py | 24 ++++++ lot.xml | 66 +++++++++++++++ move.py | 227 ++++++++++++++++++++++++++++++++++++++++++++++++++ move.xml | 198 +++++++++++++++++++++++++++++++++++++++++++ packing.py | 140 +++++++++++++++++++++++++++++++ packing.xml | 207 +++++++++++++++++++++++++++++++++++++++++++++ warehouse.py | 22 +++++ warehouse.xml | 56 +++++++++++++ 12 files changed, 1103 insertions(+) create mode 100644 __init__.py create mode 100644 __tryton__.py create mode 100644 location.py create mode 100644 location.xml create mode 100644 lot.py create mode 100644 lot.xml create mode 100644 move.py create mode 100644 move.xml create mode 100644 packing.py create mode 100644 packing.xml create mode 100644 warehouse.py create mode 100644 warehouse.xml diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000000..2c9b6efd32 --- /dev/null +++ b/__init__.py @@ -0,0 +1,31 @@ +# Copyright (c) 2004-2006 Tiny SPRL. (http://tiny.be) +# Copyright (c) 2007 Bertrand Chenal +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsability of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# garantees and support are strongly adviced to contact a Free Software +# Service Company +# +# 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 2 +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################## + +from warehouse import * +from location import * +from lot import * +from packing import * +from move import * diff --git a/__tryton__.py b/__tryton__.py new file mode 100644 index 0000000000..cf2b7194c9 --- /dev/null +++ b/__tryton__.py @@ -0,0 +1,19 @@ +{ + 'name': 'Stock Management', + 'version': '0.1', + 'author': 'B2CK', + 'email': 'info@b2ck.com', + 'website': 'http://www.tryton.org/', + 'category': 'Generic Modules', + 'description': 'Stock Management and Inventory Control', + 'depends': [ + 'partner', 'product' + ], + 'xml': [ + 'warehouse.xml', + 'location.xml', + 'move.xml', + 'lot.xml', + 'packing.xml', + ], +} diff --git a/location.py b/location.py new file mode 100644 index 0000000000..d1893a7c91 --- /dev/null +++ b/location.py @@ -0,0 +1,48 @@ +"Wharehouse" +from trytond.osv import fields, OSV + +STATES = { + 'readonly': "active == False", +} + +class Location(OSV): + "Stock Location" + _name = 'stock.location' + _order = 'name' + _description = __doc__ + name = fields.Char("Name", size=64, required=True, states=STATES,) + code = fields.Char("Code", size=32, states=STATES, select=True,) + active = fields.Boolean('Active', select=True) + usage = fields.Selection([('supplier','Supplier'), + ('customer','Customer'), + ('inventory','Inventory'), + ('procurement','Procurement'), + ('production','Production')], + 'Location type', states=STATES,) + warehouse = fields.Many2One("stock.warehouse", "Warehouse", select="1") + + def default_active(self, cursor, user, context=None): + return True + + def name_search(self, cursor, user, name, args=None, operator='ilike', + context=None, limit=80): + if not args: + args=[] + ids = self.search(cursor, user, [('code','=',name)]+ args, limit=limit, + context=context) + if not ids: + ids = self.search(cursor, user, [('name',operator,name)]+ args, + limit=limit, context=context) + result = self.name_get(cursor, user, ids, context) + return result + +Location() + + +class Warehouse(OSV): + _name = "stock.warehouse" + _inherit = "stock.warehouse" + input_location = fields.Many2One("stock.location", "Input location") + output_location = fields.Many2One("stock.location", "output location") + +Warehouse() diff --git a/location.xml b/location.xml new file mode 100644 index 0000000000..50739026f2 --- /dev/null +++ b/location.xml @@ -0,0 +1,65 @@ + + + + + stock.location + form + + + + + stock.location + tree + + + + + + + + + + + ]]> + + + + Locations + stock.location + form + + + + + + + + + + + + + + + diff --git a/lot.py b/lot.py new file mode 100644 index 0000000000..2459dd79c4 --- /dev/null +++ b/lot.py @@ -0,0 +1,24 @@ +"Wharehouse" +from trytond.osv import fields, OSV +import time + +class Lot(OSV): + "Stock Lot" + _name = 'stock.lot' + _order = 'id DESC' + _description = __doc__ + _rec_name = 'code' + + code = fields.Char("Code", size=64, select=1, readonly=1) + creation_date = fields.DateTime("Created at") + moves = fields.One2Many("stock.move", "lot", "Stock Moves") + + + def default_creation_date(self, cursor, user, context): + return time.strftime("%Y-%m-%d %H:%M:%S") + + def create(self, cursor, user, values, context): + values['code'] = self.pool.get('ir.sequence').get(cursor, user, 'stock.lot') + return super(Lot,self).create(cursor, user, values, context) + +Lot() diff --git a/lot.xml b/lot.xml new file mode 100644 index 0000000000..b05f469665 --- /dev/null +++ b/lot.xml @@ -0,0 +1,66 @@ + + + + + stock.lot + form + + + + + stock.lot + tree + + + + + + ]]> + + + + Lots + stock.lot + form + + + + + + + + + + + + + + + + Lot + stock.lot + + + + Stock Lot + stock.lot + LOT: + 7 + 1 + 1 + + + + diff --git a/move.py b/move.py new file mode 100644 index 0000000000..6fbe07cf03 --- /dev/null +++ b/move.py @@ -0,0 +1,227 @@ +"Move" +from trytond.osv import fields, OSV, ExceptORM +from trytond.wizard import Wizard, WizardOSV +import time + +STATES = { + 'readonly': "(state in ('cancel','done'))", +} + +class Move(OSV): + "Stock Move" + _name = 'stock.move' + _description = __doc__ + _order = "id DESC" + _rec_name = "product" + product = fields.Many2One("product.product", "Product", required=True, select=True, states=STATES, on_change=['product']) + uom = fields.Many2One("product.uom", "Uom", required=True, states=STATES,) + quantity = fields.Float("Quantity", digits=(12, 6), required=True, states=STATES,) + from_location =fields.Many2One("stock.location", "From Location", select=True, required=True, states=STATES,) + to_location = fields.Many2One("stock.location", "To Location", select=True, required=True, states=STATES,) + lot = fields.Many2One("stock.lot", "Lot", states=STATES, select=True) + packing = fields.Many2One("stock.packing", "Packing", states=STATES, select=True) + planned_date = fields.Date("Planned Date", states=STATES,) + effective_date = fields.Date("Effective Date", states=STATES, readonly=True) + state = fields.Selection([('draft','Draft'),('done','Done'),('cancel','Cancel'),('waiting','Waiting')], 'State', select=True, readonly=True) + + + def __init__(self): + super(Move, self).__init__() + self._rpc_allowed += [ + 'set_state_done', + 'set_state_waiting', + 'set_state_draft', + 'set_state_cancel', + ] + + def default_state(self, cursor, user, context=None): + return 'draft' + + def on_change_product(self, cursor, user, ids, value, context=None): + if 'product' in value and value['product']: + product = self.pool.get('product.product').browse(cursor, user, value['product']) + return {'uom': product.default_uom.id} + return {} + + def set_by_state(self, cursor, user, ids, from_states, values, context=None): + move_ids = self.search( + cursor, user, [('id', 'in', ids), + ('state', 'in', from_states)]) + return self.write(cursor, user, move_ids, values, context=context) + + def set_state_done(self, cursor, user, ids, context=None): + self.set_by_state( + cursor, user, ids, ['draft','waiting'], + {'state':'done', + 'effective_date': time.strftime('%Y-%m-%d %H:%M:%S')}) + + def set_state_draft(self, cursor, user, ids, context=None): + self.set_by_state( + cursor, user, ids, ['cancel','waiting'], + {'state':'draft',}) + + def set_state_cancel(self, cursor, user, ids, context=None): + self.set_by_state( + cursor, user, ids, ['done','waiting'], + {'state':'cancel',}) + def set_state_waiting(self, cursor, user, ids, context=None): + self.set_by_state( + cursor, user, ids, ['draft',], + {'state':'waiting',}) + + def unlink(self, cursor, user, ids, context=None): + move_ids = self.search( + cursor, user, [('id','in',ids),('state','in',['done','waiting','cancel'])],context) + + if move_ids: + raise ExceptORM('UserError', 'You can only delete draft moves !') + + return super(Move, self).unlink(cursor, user, ids, context=context) +Move() + + +class CreatePackingWarn(WizardOSV): + _name = 'stock.move.create_packing.warn' +CreatePackingWarn() + +class CreatePacking(Wizard): + 'Create Packing' + _name = 'stock.move.create_packing' + states = { + 'init': { + 'result': { + 'type': 'choice', + 'next_state': '_test_warn', + }, + }, + 'warning': { + 'result': { + 'type': 'form', + 'object': 'stock.move.create_packing.warn', + 'state': [ + ('create', 'Ok', 'gtk-ok', True), + ], + }, + }, + 'create': { + 'result': { + 'type': 'action', + 'action': '_create', + 'state': 'end', + }, + }, + + } + + def _test_warn(self, cursor, user, data, context=None): + move_obj = self.pool.get('stock.move') + moves_with_packing = move_obj.search( + cursor, user, [('id','in',data['ids']),('packing','!=',None)], + context=context) + if moves_with_packing: + data['ids'] = [ i for i in data['ids'] if i not in moves_with_packing ] + return 'warning' + else: + return 'create' + + def _create(self, cursor, user, data, context=None): + packing_obj = self.pool.get('stock.packing.in') + packing_obj.create( + cursor, user, {'type': 'in', 'moves': [('set',data['ids'])]}, + context=context) + + model_data_obj = self.pool.get('ir.model.data') + act_window_obj = self.pool.get('ir.action.act_window') + + model_data_ids = model_data_obj.search(cursor, user, [ + ('fs_id', '=', 'act_packing_form'), + ('module', '=', 'stock'), + ], limit=1, context=context) + model_data = model_data_obj.browse(cursor, user, model_data_ids[0], + context=context) + res = act_window_obj.read(cursor, user, model_data.db_id, context=context) + + return res + +CreatePacking() + +class ProductByLocation(OSV): + "Product by Location" + _name = "stock.product_by_location" + _description = __doc__ + _auto = False + + location = fields.Many2One("stock.location","Location", readonly=True, select=True) + product = fields.Many2One("product.product", "Product", readonly=True, select=True) + quantity = fields.Float("Quantity", digits=(12, 6), readonly=True) + uom = fields.Many2One("product.uom", "Uom", readonly=True,) + + + def init(self, cursor, module): + cursor.execute( + "create or replace view stock_product_by_location as ( "\ + "select location, product, uom, sum(quantity) as quantity, "\ + "min(id) as id "\ + "from ( "\ + "SELECT to_location as location, product, uom, "\ + "sum(quantity) as quantity, min(id) as id "\ + "FROM stock_move "\ + "WHERE state = 'done'"\ + "GROUP by to_location, product ,uom "\ + "UNION "\ + "SELECT from_location as location, product, uom, "\ + "-sum(quantity) as quantity, min(-id) as id "\ + "FROM stock_move "\ + "WHERE state = 'done'"\ + "GROUP by from_location, product, uom "\ + ") "\ + "as T group by T.location, T.product, T.uom)" ) + +ProductByLocation() + + +class ProductByWarehouse(OSV): + "Product by Warehouse" + _name = "stock.product_by_warehouse" + _description = __doc__ + _auto = False + + warehouse = fields.Many2One("stock.warehouse","Warehouse", readonly=True, select=True) + product = fields.Many2One("product.product", "Product", readonly=True, select=True) + quantity = fields.Float("Quantity", digits=(12, 6), readonly=True) + uom = fields.Many2One("product.uom", "Uom", readonly=True,) + + + def init(self, cursor, module): + cursor.execute( + "create or replace view stock_product_by_warehouse as ( "\ + "select warehouse, product, uom, sum(quantity) as quantity, "\ + "min(id) as id "\ + "from ( "\ + "SELECT l_from.warehouse as warehouse, m.product, m.uom, "\ + "sum(m.quantity) as quantity, min(m.id) as id "\ + "FROM stock_move m "\ + "join stock_location l_from on (l_from.id=m.from_location) "\ + "join stock_location l_to on (l_to.id=m.to_location) "\ + "WHERE m.state = 'done' and ( "\ + "(l_to.warehouse != l_from.warehouse) or "\ + "(l_to.warehouse is null and l_from.warehouse is not null) or "\ + "(l_from.warehouse is null and l_to.warehouse is not null) "\ + ") "\ + "GROUP by l_from.warehouse, m.product ,m.uom "\ + "UNION "\ + "SELECT l_to.warehouse as warehouse, m.product, m.uom, "\ + "-sum(m.quantity) as quantity, -min(m.id) as id "\ + "FROM stock_move m "\ + "join stock_location l_to on (l_to.id=m.to_location)"\ + "join stock_location l_from on (l_from.id=m.from_location) "\ + "WHERE m.state = 'done' and ( "\ + "(l_to.warehouse != l_from.warehouse) or "\ + "(l_to.warehouse is null and l_from.warehouse is not null) or "\ + "(l_from.warehouse is null and l_to.warehouse is not null) "\ + ") "\ + "GROUP by l_to.warehouse, m.product, m.uom "\ + ") "\ + "as T group by T.warehouse, T.product, T.uom)" ) + +ProductByWarehouse() diff --git a/move.xml b/move.xml new file mode 100644 index 0000000000..786f9ee0ee --- /dev/null +++ b/move.xml @@ -0,0 +1,198 @@ + + + + + stock.move + form + + +