diff --git a/customer_return_restocking_list.odt b/customer_return_restocking_list.odt index d9a9ff4e59..c9429d0d04 100644 Binary files a/customer_return_restocking_list.odt and b/customer_return_restocking_list.odt differ diff --git a/delivery_note.odt b/delivery_note.odt index 0e83fb359c..ebe4b93ba4 100644 Binary files a/delivery_note.odt and b/delivery_note.odt differ diff --git a/internal_shipment.odt b/internal_shipment.odt index 8eabf98ae1..082496558a 100644 Binary files a/internal_shipment.odt and b/internal_shipment.odt differ diff --git a/picking_list.odt b/picking_list.odt index 6edecd13fe..aceef03772 100644 Binary files a/picking_list.odt and b/picking_list.odt differ diff --git a/shipment.py b/shipment.py index bd267e8da9..8f69f1f31f 100644 --- a/shipment.py +++ b/shipment.py @@ -2,6 +2,7 @@ # this repository contains the full copyright notices and license terms. import operator import itertools +import functools import datetime from sql import Table from sql.functions import Overlay, Position @@ -2106,11 +2107,11 @@ class DeliveryNote(CompanyReport): __name__ = 'stock.shipment.out.delivery_note' @classmethod - def parse(cls, report, objects, data, localcontext): - localcontext['product_name'] = lambda product_id, language: \ + def get_context(cls, records, data): + report_context = super(DeliveryNote, cls).get_context(records, data) + report_context['product_name'] = lambda product_id, language: \ cls.product_name(product_id, language) - return super(DeliveryNote, cls).parse(report, objects, data, - localcontext) + return report_context @classmethod def product_name(cls, product_id, language): @@ -2119,189 +2120,61 @@ class DeliveryNote(CompanyReport): return Product(product_id).rec_name -class PickingList(CompanyReport): +class ShipmentReport(CompanyReport): + move_attribute = 'inventory_moves' + + @classmethod + def get_context(cls, records, data): + report_context = super(ShipmentReport, cls).get_context(records, data) + + compare_context = cls.get_compare_context(records, data) + sorted_moves = {} + for shipment in records: + sorted_moves[shipment.id] = sorted( + getattr(shipment, cls.move_attribute), + key=functools.partial(cls.get_compare_key, compare_context)) + report_context['moves'] = sorted_moves + + return report_context + + @classmethod + def get_compare_context(cls, objects, data): + from_location_ids = set() + to_location_ids = set() + for obj in objects: + for move in getattr(obj, cls.move_attribute): + from_location_ids.add(move.from_location.id) + to_location_ids.add(move.to_location.id) + + return { + 'from_location_ids': list(from_location_ids), + 'to_location_ids': list(to_location_ids), + } + + @staticmethod + def get_compare_key(compare_context, move): + from_location_ids = compare_context['from_location_ids'] + to_location_ids = compare_context['to_location_ids'] + return [from_location_ids.index(move.from_location.id), + to_location_ids.index(move.to_location.id)] + + +class PickingList(ShipmentReport): 'Picking List' __name__ = 'stock.shipment.out.picking_list' - @classmethod - def parse(cls, report, objects, data, localcontext): - compare_context = cls.get_compare_context(report, objects, data) - sorted_moves = {} - for shipment in objects: - sorted_moves[shipment.id] = sorted( - shipment.inventory_moves, - lambda x, y: cmp(cls.get_compare_key(x, compare_context), - cls.get_compare_key(y, compare_context)) - ) - - localcontext['moves'] = sorted_moves - - return super(PickingList, cls).parse(report, objects, data, - localcontext) - - @staticmethod - def get_compare_context(report, objects, data): - Location = Pool().get('stock.location') - from_location_ids = set() - to_location_ids = set() - for obj in objects: - for move in obj.inventory_moves: - from_location_ids.add(move.from_location.id) - to_location_ids.add(move.to_location.id) - - from_locations = Location.browse(list(from_location_ids)) - to_locations = Location.browse(list(to_location_ids)) - - return { - 'from_location_ids': [l.id for l in from_locations], - 'to_location_ids': [l.id for l in to_locations], - } - - @staticmethod - def get_compare_key(move, compare_context): - from_location_ids = compare_context['from_location_ids'] - to_location_ids = compare_context['to_location_ids'] - return [from_location_ids.index(move.from_location.id), - to_location_ids.index(move.to_location.id)] - - -class SupplierRestockingList(CompanyReport): +class SupplierRestockingList(ShipmentReport): 'Supplier Restocking List' __name__ = 'stock.shipment.in.restocking_list' - @classmethod - def parse(cls, report, objects, data, localcontext): - compare_context = cls.get_compare_context(report, objects, data) - sorted_moves = {} - for shipment in objects: - sorted_moves[shipment.id] = sorted( - shipment.inventory_moves, - lambda x, y: cmp(cls.get_compare_key(x, compare_context), - cls.get_compare_key(y, compare_context)) - ) - - localcontext['moves'] = sorted_moves - - return super(SupplierRestockingList, cls).parse(report, objects, - data, localcontext) - - @staticmethod - def get_compare_context(report, objects, data): - Location = Pool().get('stock.location') - from_location_ids = set() - to_location_ids = set() - for obj in objects: - for move in obj.inventory_moves: - from_location_ids.add(move.from_location.id) - to_location_ids.add(move.to_location.id) - - from_locations = Location.browse(list(from_location_ids)) - to_locations = Location.browse(list(to_location_ids)) - - return { - 'from_location_ids': [l.id for l in from_locations], - 'to_location_ids': [l.id for l in to_locations], - } - - @staticmethod - def get_compare_key(move, compare_context): - from_location_ids = compare_context['from_location_ids'] - to_location_ids = compare_context['to_location_ids'] - return [from_location_ids.index(move.from_location.id), - to_location_ids.index(move.to_location.id)] - - -class CustomerReturnRestockingList(CompanyReport): +class CustomerReturnRestockingList(ShipmentReport): 'Customer Return Restocking List' __name__ = 'stock.shipment.out.return.restocking_list' - @classmethod - def parse(cls, report, objects, data, localcontext): - compare_context = cls.get_compare_context(report, objects, data) - sorted_moves = {} - for shipment in objects: - sorted_moves[shipment.id] = sorted( - shipment.inventory_moves, - lambda x, y: cmp(cls.get_compare_key(x, compare_context), - cls.get_compare_key(y, compare_context)) - ) - - localcontext['moves'] = sorted_moves - - return super(CustomerReturnRestockingList, cls).parse(report, - objects, data, localcontext) - - @staticmethod - def get_compare_context(report, objects, data): - Location = Pool().get('stock.location') - from_location_ids = set() - to_location_ids = set() - for obj in objects: - for move in obj.inventory_moves: - from_location_ids.add(move.from_location.id) - to_location_ids.add(move.to_location.id) - - from_locations = Location.browse(list(from_location_ids)) - to_locations = Location.browse(list(to_location_ids)) - - return { - 'from_location_ids': [l.id for l in from_locations], - 'to_location_ids': [l.id for l in to_locations], - } - - @staticmethod - def get_compare_key(move, compare_context): - from_location_ids = compare_context['from_location_ids'] - to_location_ids = compare_context['to_location_ids'] - return [from_location_ids.index(move.from_location.id), - to_location_ids.index(move.to_location.id)] - - -class InteralShipmentReport(CompanyReport): +class InteralShipmentReport(ShipmentReport): 'Interal Shipment Report' __name__ = 'stock.shipment.internal.report' - - @classmethod - def parse(cls, report, objects, data, localcontext=None): - compare_context = cls.get_compare_context(report, objects, data) - - sorted_moves = {} - for shipment in objects: - sorted_moves[shipment.id] = sorted( - shipment.moves, - lambda x, y: cmp(cls.get_compare_key(x, compare_context), - cls.get_compare_key(y, compare_context)) - ) - - localcontext['moves'] = sorted_moves - - return super(InteralShipmentReport, cls).parse(report, objects, - data, localcontext) - - @staticmethod - def get_compare_context(report, objects, data): - Location = Pool().get('stock.location') - from_location_ids = set() - to_location_ids = set() - for obj in objects: - for move in obj.moves: - from_location_ids.add(move.from_location.id) - to_location_ids.add(move.to_location.id) - - from_locations = Location.browse(list(from_location_ids)) - to_locations = Location.browse(list(to_location_ids)) - - return { - 'from_location_ids': [l.id for l in from_locations], - 'to_location_ids': [l.id for l in to_locations], - } - - @staticmethod - def get_compare_key(move, compare_context): - from_location_ids = compare_context['from_location_ids'] - to_location_ids = compare_context['to_location_ids'] - return [from_location_ids.index(move.from_location.id), - to_location_ids.index(move.to_location.id)] + move_attribute = 'moves' diff --git a/supplier_restocking_list.odt b/supplier_restocking_list.odt index cfa779b73a..4f7ac4400c 100644 Binary files a/supplier_restocking_list.odt and b/supplier_restocking_list.odt differ diff --git a/tests/scenario_stock_reporting.rst b/tests/scenario_stock_reporting.rst new file mode 100644 index 0000000000..9c980e1b8c --- /dev/null +++ b/tests/scenario_stock_reporting.rst @@ -0,0 +1,188 @@ +================================= +Stock Shipment Reporting Scenario +================================= + +Imports:: + + >>> import datetime + >>> from dateutil.relativedelta import relativedelta + >>> from decimal import Decimal + >>> from proteus import config, Model, Wizard, Report + >>> from trytond.modules.company.tests.tools import create_company, \ + ... get_company + >>> today = datetime.date.today() + >>> yesterday = today - relativedelta(days=1) + +Create database:: + + >>> config = config.set_trytond() + >>> config.pool.test = True + +Install stock Module:: + + >>> Module = Model.get('ir.module.module') + >>> module, = Module.find([('name', '=', 'stock')]) + >>> module.click('install') + >>> Wizard('ir.module.module.install_upgrade').execute('upgrade') + +Create company:: + + >>> _ = create_company() + >>> company = get_company() + +Reload the context:: + + >>> User = Model.get('res.user') + >>> config._context = User.get_preferences(True, config.context) + +Create customer & supplier:: + + >>> Party = Model.get('party.party') + >>> customer = Party(name='Customer') + >>> customer.save() + >>> supplier = Party(name='Supplier') + >>> supplier.save() + +Create category:: + + >>> ProductCategory = Model.get('product.category') + >>> category = ProductCategory(name='Category') + >>> category.save() + +Create product:: + + >>> ProductUom = Model.get('product.uom') + >>> ProductTemplate = Model.get('product.template') + >>> Product = Model.get('product.product') + >>> unit, = ProductUom.find([('name', '=', 'Unit')]) + >>> product = Product() + >>> template = ProductTemplate() + >>> template.name = 'Product' + >>> template.category = category + >>> template.default_uom = unit + >>> template.type = 'goods' + >>> template.list_price = Decimal('20') + >>> template.cost_price = Decimal('8') + >>> template.save() + >>> product.template = template + >>> product.save() + +Get stock locations:: + + >>> Location = Model.get('stock.location') + >>> warehouse_loc, = Location.find([('code', '=', 'WH')]) + >>> supplier_loc, = Location.find([('code', '=', 'SUP')]) + >>> customer_loc, = Location.find([('code', '=', 'CUS')]) + >>> input_loc, = Location.find([('code', '=', 'IN')]) + >>> output_loc, = Location.find([('code', '=', 'OUT')]) + >>> storage_loc, = Location.find([('code', '=', 'STO')]) + >>> lost_loc, = Location.find([('type', '=', 'lost_found')]) + +Create Shipment In:: + + >>> ShipmentIn = Model.get('stock.shipment.in') + >>> shipment_in = ShipmentIn() + >>> shipment_in.planned_date = today + >>> shipment_in.supplier = supplier + >>> shipment_in.warehouse = warehouse_loc + >>> shipment_in.company = company + +Receive a bunch of products:: + + >>> move = shipment_in.incoming_moves.new() + >>> move.product = product + >>> move.uom = unit + >>> move.quantity = 100 + >>> move.from_location = supplier_loc + >>> move.to_location = input_loc + >>> move.company = company + >>> move.unit_price = Decimal('1') + >>> move.currency = company.currency + >>> shipment_in.save() + >>> shipment_in.click('receive') + >>> shipment_in.click('done') + +Testing the report:: + + >>> supplier_restocking_list = Report('stock.shipment.in.restocking_list') + >>> ext, _, _, name = supplier_restocking_list.execute([shipment_in], {}) + >>> ext + u'odt' + >>> name + u'Restocking List' + +Create Shipment Out:: + + >>> ShipmentOut = Model.get('stock.shipment.out') + >>> shipment_out = ShipmentOut() + >>> shipment_out.planned_date = today + >>> shipment_out.customer = customer + >>> shipment_out.warehouse = warehouse_loc + >>> shipment_out.company = company + +Add two shipment lines of same product and go through the workflow:: + + >>> move = shipment_out.outgoing_moves.new() + >>> move.product = product + >>> move.uom = unit + >>> move.quantity = 1 + >>> move.from_location = output_loc + >>> move.to_location = customer_loc + >>> move.company = company + >>> move.unit_price = Decimal('1') + >>> move.currency = company.currency + >>> shipment_out.save() + >>> shipment_out.click('wait') + >>> shipment_out.click('assign_try') + True + >>> shipment_out.click('pack') + >>> shipment_out.click('done') + +Testing the reports:: + + >>> delivery_note = Report('stock.shipment.out.delivery_note') + >>> ext, _, _, name = delivery_note.execute([shipment_out], {}) + >>> ext + u'odt' + >>> name + u'Delivery Note' + + >>> picking_list = Report('stock.shipment.out.picking_list') + >>> ext, _, _, name = picking_list.execute([shipment_out], {}) + >>> ext + u'odt' + >>> name + u'Picking List' + +Create an internal shipment:: + + >>> ShipmentInternal = Model.get('stock.shipment.internal') + >>> shipment_internal = ShipmentInternal() + >>> shipment_internal.planned_date = today + >>> shipment_internal.company = company + >>> shipment_internal.from_location = storage_loc + >>> shipment_internal.to_location = lost_loc + >>> move = shipment_internal.moves.new() + >>> move.product = product + >>> move.uom = unit + >>> move.quantity = 1 + >>> move.from_location = storage_loc + >>> move.to_location = lost_loc + >>> move.company = company + >>> move.unit_price = Decimal('1') + >>> move.currency = company.currency + >>> shipment_internal.save() + >>> shipment_internal.click('wait') + >>> shipment_internal.click('assign_try') + True + >>> shipment_internal.click('done') + +Testing the report:: + + >>> internal_report = Report('stock.shipment.internal.report') + >>> ext, _, _, name = internal_report.execute([shipment_internal], {}) + >>> ext + u'odt' + >>> name + u'Internal Shipment' + diff --git a/tests/test_stock.py b/tests/test_stock.py index 37995996dd..7a5c1394ef 100644 --- a/tests/test_stock.py +++ b/tests/test_stock.py @@ -686,4 +686,7 @@ def suite(): suite.addTests(doctest.DocFileSuite('scenario_stock_shipment_internal.rst', setUp=doctest_setup, tearDown=doctest_teardown, encoding='utf-8', optionflags=doctest.REPORT_ONLY_FIRST_FAILURE)) + suite.addTests(doctest.DocFileSuite('scenario_stock_reporting.rst', + setUp=doctest_setup, tearDown=doctest_teardown, encoding='utf-8', + optionflags=doctest.REPORT_ONLY_FIRST_FAILURE)) return suite