Add exporting

This commit is contained in:
Sergio Morillo 2018-01-02 23:13:24 +01:00
parent 37bc321ce9
commit b041f3ef66
6 changed files with 291 additions and 12 deletions

View file

@ -2,7 +2,8 @@
# the full copyright notices and license terms.
from trytond.pool import Pool
from .stock import (LocationDailyReportOpen, LocationDailyReportData,
LocationDailyReport, LocationDailyReportRequired, StockConfiguration)
LocationDailyReport, LocationDailyReportRequired, StockConfiguration,
LocationDailyReportExport)
def register():
@ -10,6 +11,7 @@ def register():
LocationDailyReportData,
StockConfiguration,
LocationDailyReportRequired,
LocationDailyReportExport,
module='stock_daily_report', type_='model')
Pool.register(
LocationDailyReportOpen,

View file

@ -30,6 +30,30 @@ msgctxt "report:stock.location.daily_report:"
msgid "TOTAL CONSUME"
msgstr "CANTIDAD DE CONSUMOS"
msgctxt "report:stock.location.daily_report:"
msgid "DATE"
msgstr "FECHA"
msgctxt "report:stock.location.daily_report:"
msgid "LOCATION"
msgstr "UBICACIÓN"
msgctxt "report:stock.location.daily_report:"
msgid "CONCEPT"
msgstr "CONCEPTO"
msgctxt "report:stock.location.daily_report:"
msgid "QUANTITY"
msgstr "CANTIDAD"
msgctxt "report:stock.location.daily_report:"
msgid "INITIAL STOCK"
msgstr "STOCK INICIAL"
msgctxt "report:stock.location.daily_report:"
msgid "END STOCK"
msgstr "STOCK FINAL"
msgctxt "model:ir.action,name:act_daily_report"
msgid "Daily Report"
msgstr "Informe Diario"
@ -66,10 +90,18 @@ msgctxt "field:stock.location.daily_report.required,show_summary:"
msgid "Show summary"
msgstr "Mostrar resumen"
msgctxt "field:stock.location.daily_report.export,file:"
msgid "File"
msgstr "Archivo"
msgctxt "wizard_button:stock.location.daily_report.open,start,print_:"
msgid "Print"
msgstr "Imprimir"
msgctxt "wizard_button:stock.location.daily_report.open,start,export:"
msgid "Export"
msgstr "Exportar"
msgctxt "wizard_button:stock.location.daily_report.open,start,end:"
msgid "Cancel"
msgstr "Cancelar"

159
stock.py
View file

@ -1,16 +1,23 @@
# The COPYRIGHT file at the top level of this repository contains the full
# copyright notices and license terms.
import csv
import ast
from trytond.model import ModelView, fields
from trytond.wizard import Wizard, StateView, StateReport, Button
import types
import numbers
import tempfile
from trytond.model import ModelView, Model, fields
from trytond.wizard import (Wizard, StateView, StateReport,
StateTransition, Button)
from trytond.modules.company import CompanyReport
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Eval, Bool
from trytond.report import TranslateFactory
import datetime
from trytond.transaction import Transaction
__all__ = ['LocationDailyReportOpen', 'LocationDailyReportData',
'LocationDailyReport', 'StockConfiguration', 'LocationDailyReportRequired']
'LocationDailyReport', 'StockConfiguration', 'LocationDailyReportRequired',
'LocationDailyReportExport']
class LocationDailyReportRequired(ModelView):
@ -43,6 +50,14 @@ class LocationDailyReportData(ModelView):
})
class LocationDailyReportExport(ModelView):
"""Export Daily report"""
__name__ = 'stock.location.daily_report.export'
file = fields.Binary('File', readonly=True, filename='filename')
filename = fields.Char('Filename', readonly=True)
class LocationDailyReportOpen(Wizard):
"""Stock Location Daily Report Open"""
__name__ = 'stock.location.daily_report.open'
@ -50,9 +65,14 @@ class LocationDailyReportOpen(Wizard):
start = StateView('stock.location.daily_report.data',
'stock_daily_report.location_daily_report_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Export', 'export', 'tryton-save'),
Button('Print', 'print_', 'tryton-print', default=True)
])
print_ = StateReport('stock.location.daily_report')
export = StateTransition()
result = StateView('stock.location.daily_report.export',
'stock_daily_report.location_daily_report_export_view_form', [
Button('Close', 'end', 'tryton-close')])
def default_start(self, fields):
pool = Pool()
@ -70,9 +90,13 @@ class LocationDailyReportOpen(Wizard):
return default
def do_print_(self, action):
data = self._get_data()
return action, data
def _get_data(self):
show_locations = {l.location.id: l.show_summary
for l in self.start.locations}
data = {
return {
'date': self.start.date,
'warehouse': self.start.warehouse.id,
'categories': [c.id for c in self.start.categories],
@ -81,7 +105,124 @@ class LocationDailyReportOpen(Wizard):
'ids': [location_show.location.id
for location_show in self.start.locations]
}
return action, data
def transition_export(self):
pool = Pool()
DailyReport = pool.get('stock.location.daily_report', type='report')
ctx = DailyReport.get_context(
[l.location for l in self.start.locations], self._get_data())
self.result.file = open(self.generate_csv_report(ctx), 'rU').read()
return 'result'
def default_result(self, fields):
file_ = self.result.file
cast = self.result.__class__.file.cast
self.result.file = False # No need to store it in session
return {
'file': cast(file_) if file_ else None,
'filename': 'stock.csv'
}
def generate_csv_report(self, ctx):
pool = Pool()
Translation = pool.get('ir.translation')
translate = TranslateFactory('stock.location.daily_report',
Transaction().language, Translation)
decimal_point = Transaction().context['locale']['decimal_point']
fileno, fname = tempfile.mkstemp('.csv', 'tryton_')
file_p = open(fname, 'wb+')
if decimal_point == ',':
writer = csv.writer(file_p, delimiter=';')
else:
writer = csv.writer(file_p)
def format_values(values):
new_values = []
for val in values:
if isinstance(type(val), types.StringType):
new_values.append(val.replace('\n', ' ').replace(
'\t', ' '))
elif isinstance(val, numbers.Number):
new_values.append(str(val))
elif isinstance(val, unicode):
new_values.append(val.encode('utf-8'))
else:
new_values.append(val)
return new_values
writer.writerow(format_values(self._get_csv_report_header(ctx,
translate)))
for product, data in ctx['products_locations_moves'].iteritems():
for location, models in data.iteritems():
# initial stock
init_row = self._get_csv_report_row(location, product, None,
model=translate('INITIAL STOCK'),
effective_date=self.start.date,
quantity=ctx['products_stock'].get(product.id, {}).get(
location.id, 0))
writer.writerow(format_values(init_row))
for model, moves in models.iteritems():
if model == 'others':
writer.writerow(format_values(self._get_csv_report_row(
location, product, None, model='',
effective_date=self.start.date,
quantity=moves[0])))
continue
_moves = moves[1:]
for move in _moves:
row = self._get_csv_report_row(location,
product, move, model=model.rec_name if
isinstance(model, Model) else model)
writer.writerow(format_values(row))
# end stock
writer.writerow(format_values(
self._get_csv_report_row(location, product, None,
model=translate('END STOCK'),
effective_date=self.start.date,
quantity=ctx['total_stock'][location.id][product.id])))
file_p.close()
return fname
def _get_csv_report_header(self, ctx, translate):
row = [
translate('DATE'),
translate('LOCATION'),
translate('PRODUCT'),
translate('CONCEPT'),
translate('QUANTITY'),
]
return row
def _get_csv_report_row(self, location, product, move=None, **kwargs):
assert product or move
assert move or (set(['quantity', 'effective_date']) & set(
kwargs.keys()) == set(['quantity', 'effective_date']))
if product is None:
product = move.product
_concept = kwargs['model']
if move is not None and (move.shipment or move.origin):
_concept = '%s %s' % (_concept,
(move.shipment or move.origin).rec_name)
res = [
move.effective_date if move else kwargs['effective_date'],
location.rec_name,
product.name,
_concept,
(move.internal_quantity * (
1 if location.id == move.to_location.id else -1)
) if move else kwargs.get('quantity', 0)
]
return res
class LocationDailyReport(CompanyReport):
@ -101,6 +242,8 @@ class LocationDailyReport(CompanyReport):
report_context['total_consume'] = (lambda product, location,
products_locations_moves: cls.get_total_consume(product, location,
products_locations_moves))
report_context['get_ordered_products'] = (lambda products:
cls.get_ordered_products(products))
products = set()
previous_day = data['date'] - datetime.timedelta(days=1)
moves_by_location = []
@ -152,7 +295,7 @@ class LocationDailyReport(CompanyReport):
@classmethod
def get_quantity(cls, move, location):
if move.to_location == location.id:
if move.to_location.id == location.id:
return move.internal_quantity
else:
return -move.internal_quantity
@ -205,6 +348,10 @@ class LocationDailyReport(CompanyReport):
clauses.append(('product.categories', 'in', categories))
return set(Move.search(clauses))
@classmethod
def get_ordered_products(cls, products):
return sorted(products, key=lambda p: p.name)
class StockConfiguration:
__metaclass__ = PoolMeta

View file

@ -18,6 +18,11 @@ this repository contains the full copyright notices and license terms. -->
<field name="type">tree</field>
<field name="name">location_daily_report_required_list</field>
</record>
<record model="ir.ui.view" id="location_daily_report_export_view_form">
<field name="model">stock.location.daily_report.export</field>
<field name="type">form</field>
<field name="name">location_daily_report_export_form</field>
</record>
<record model="ir.action.wizard" id="act_daily_report">
<field name="name">Daily Report</field>
<field name="wiz_name">stock.location.daily_report.open</field>

View file

@ -5,7 +5,11 @@ Stock Daily Report 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()
Create a database::
@ -22,17 +26,99 @@ Install stock_daily_report::
>>> module.click('install')
>>> Wizard('ir.module.install_upgrade').execute('upgrade')
Create a Stock Location::
Create company::
>>> _ = create_company()
>>> company = get_company()
Reload the context::
>>> User = Model.get('res.user')
>>> config._context = User.get_preferences(True, config.context)
Get stock locations::
>>> Location = Model.get('stock.location')
>>> input_loc, = Location.find([('code', '=', 'IN')])
>>> supplier_loc, = Location.find([('code', '=', 'SUP')])
>>> storage_loc, = Location.find([('code', '=', 'STO')])
>>> customer_loc, = Location.find([('code', '=', 'CUS')])
>>> wh, = Location.find([('type', '=', 'warehouse')])
Create products::
>>> ProductUom = Model.get('product.uom')
>>> ProductTemplate = Model.get('product.template')
>>> unit, = ProductUom.find([('name', '=', 'Unit')])
>>> template = ProductTemplate()
>>> template.name = 'Product'
>>> template.default_uom = unit
>>> template.type = 'goods'
>>> template.list_price = Decimal('300')
>>> template.cost_price = Decimal('80')
>>> template.cost_price_method = 'average'
>>> template.save()
>>> product, = template.products
>>> kg, = ProductUom.find([('name', '=', 'Kilogram')])
>>> template2 = ProductTemplate()
>>> template2.name = 'Product 2'
>>> template2.default_uom = kg
>>> template2.type = 'goods'
>>> template2.list_price = Decimal('140')
>>> template2.cost_price = Decimal('60')
>>> template2.cost_price_method = 'average'
>>> template2.save()
>>> product2, = template2.products
Fill storage::
>>> StockMove = Model.get('stock.move')
>>> incoming_move = StockMove()
>>> incoming_move.product = product
>>> incoming_move.uom = unit
>>> incoming_move.quantity = 1
>>> incoming_move.from_location = supplier_loc
>>> incoming_move.to_location = storage_loc
>>> incoming_move.planned_date = today
>>> incoming_move.effective_date = today
>>> incoming_move.company = company
>>> incoming_move.unit_price = Decimal('100')
>>> incoming_move.currency = company.currency
>>> incoming_moves = [incoming_move]
>>> incoming_move = StockMove()
>>> incoming_move.product = product2
>>> incoming_move.uom = kg
>>> incoming_move.quantity = 2.5
>>> incoming_move.from_location = supplier_loc
>>> incoming_move.to_location = storage_loc
>>> incoming_move.planned_date = today
>>> incoming_move.effective_date = today
>>> incoming_move.company = company
>>> incoming_move.unit_price = Decimal('70')
>>> incoming_move.currency = company.currency
>>> incoming_moves.append(incoming_move)
>>> StockMove.click(incoming_moves, 'do')
Testing the report::
>>> LocationShow = Model.get('stock.location.daily_report.required')
>>> daily_report = Wizard('stock.location.daily_report.open')
>>> daily_report.form.date = today
>>> ls = LocationShow(location=input_loc, show_report=True)
>>> daily_report.form.warehouse = input_loc
>>> daily_report.form.warehouse = wh
>>> ls = LocationShow(location=storage_loc, show_summary=True)
>>> daily_report.form.all_categories = True
>>> daily_report.form.locations.append(ls)
>>> daily_report.execute('print_')
>>> daily_report.execute('print_')
Testing the export::
>>> daily_report = Wizard('stock.location.daily_report.open')
>>> daily_report.form.date = today
>>> daily_report.form.warehouse = wh
>>> ls = LocationShow(location=storage_loc, show_summary=True)
>>> daily_report.form.all_categories = True
>>> daily_report.form.locations.append(ls)
>>> daily_report.execute('export')
>>> daily_report.form.file
bytearray(b'DATE,LOCATION,PRODUCT,CONCEPT,QUANTITY\n2018-01-02,Storage Zone,Product 2,INITIAL STOCK,0\n2018-01-02,Storage Zone,Product 2,,2.5\n2018-01-02,Storage Zone,Product 2,END STOCK,2.5\n2018-01-02,Storage Zone,Product,INITIAL STOCK,0\n2018-01-02,Storage Zone,Product,,1.0\n2018-01-02,Storage Zone,Product,END STOCK,1.0\n')

View file

@ -0,0 +1,7 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<form col="2">
<label name="file"/>
<field name="file"/>
</form>