mirror of
https://gitlab.com/datalifeit/trytond-stock_daily_report
synced 2023-12-13 20:50:26 +01:00
Add exporting
This commit is contained in:
parent
37bc321ce9
commit
b041f3ef66
6 changed files with 291 additions and 12 deletions
|
@ -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,
|
||||
|
|
32
locale/es.po
32
locale/es.po
|
@ -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
159
stock.py
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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')
|
7
view/location_daily_report_export_form.xml
Normal file
7
view/location_daily_report_export_form.xml
Normal 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>
|
Loading…
Reference in a new issue