initial commit

This commit is contained in:
??ngel ??lvarez 2015-11-27 12:12:05 +01:00
commit 46bb29b659
15 changed files with 662 additions and 0 deletions

1
CHANGELOG Normal file
View File

@ -0,0 +1 @@
* Initial release

14
COPYRIGHT Normal file
View File

@ -0,0 +1,14 @@
Copyright (C) 2014 NaN·tic
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/>.

27
INSTALL Normal file
View File

@ -0,0 +1,27 @@
Installing
==========
Prerequisites
-------------
* see setup.py file
Installation
------------
Once you've downloaded and unpacked the source release, enter the
directory where the archive was unpacked, and run:
python setup.py install
Note that you may need administrator/root privileges for this step, as
this command will by default attempt to install module to the Python
site-packages directory on your system.
For advanced options, please refer to the easy_install and/or the distutils
documentation:
http://peak.telecommunity.com/DevCenter/EasyInstall
http://docs.python.org/inst/inst.html
To use without installation, extract the archive into ``trytond/modules``.

13
MANIFEST.in Normal file
View File

@ -0,0 +1,13 @@
include INSTALL
include README
include COPYRIGHT
include CHANGELOG
include LICENSE
include tryton.cfg
include *.xml
include view/*.xml
include *.odt
include locale/*.po
include doc/*
include icons/*
include tests/*.rst

14
__init__.py Normal file
View File

@ -0,0 +1,14 @@
# The COPYRIGHT file at the top level of this repository contains the full
# copyright notices and license terms.
from trytond.pool import Pool
from .production import *
def register():
Pool.register(
Production,
SplitProductionStart,
module='production_split_serial_number', type_='model')
Pool.register(
SplitProduction,
module='production_split_serial_number', type_='wizard')

7
doc/es/produciton.rst Normal file
View File

@ -0,0 +1,7 @@
#:inside:production/production:section:split_production#
Si el producto esta marcado como |serial_number| se creará automáticamente
un lote para las salidas de cada una de las producciones resultantes.
.. |serial_number| field:: product.template/serial_number

7
locale/ca_ES.po Normal file
View File

@ -0,0 +1,7 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:production.split.start,quantity_readonly:"
msgid "Quantity Readonly"
msgstr "Quantitat només lectura"

7
locale/es_ES.po Normal file
View File

@ -0,0 +1,7 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:production.split.start,quantity_readonly:"
msgid "Quantity Readonly"
msgstr "Cantitat solo lectura"

63
production.py Normal file
View File

@ -0,0 +1,63 @@
# The COPYRIGHT file at the top level of this repository contains the full
# copyright notices and license terms.
from trytond.model import fields
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Eval
from trytond.transaction import Transaction
__all__ = ['Production', 'SplitProductionStart', 'SplitProduction']
__metaclass__ = PoolMeta
class Production:
__name__ = 'production'
def _split_inputs_outputs(self, factor):
pool = Pool()
Lot = pool.get('stock.lot')
super(Production, self)._split_inputs_outputs(factor)
for output in self.outputs:
if not output.product.serial_number:
continue
if output.quantity != 1.0 or output.lot:
continue
if hasattr(output, 'get_production_output_lot'):
lot = output.get_production_output_lot()
lot.save()
else:
lot = Lot(product=output.product)
lot.save()
if lot:
output.lot = lot
output.save()
class SplitProductionStart:
__name__ = 'production.split.start'
quantity_readonly = fields.Boolean('Quantity Readonly')
@classmethod
def __setup__(cls):
super(SplitProductionStart, cls).__setup__()
if 'quantity_readonly' not in cls.quantity.depends:
readonly = cls.quantity.states.get('readonly', False)
cls.quantity.states.update({
'readonly': Eval('quantity_readonly') | readonly,
})
cls.quantity.depends.append('quantity_readonly')
class SplitProduction:
'Split Production'
__name__ = 'production.split'
def default_start(self, fields):
pool = Pool()
Production = pool.get('production')
default = super(SplitProduction, self).default_start(fields)
production = Production(Transaction().context['active_id'])
if production.product and production.product.serial_number:
default['quantity'] = 1.0
default['quantity_readonly'] = True
return default

99
setup.py Normal file
View File

@ -0,0 +1,99 @@
#!/usr/bin/env python
# encoding: utf-8
from setuptools import setup
import re
import os
import ConfigParser
MODULE = 'production_split_serial_number'
PREFIX = 'nantic'
MODULE2PREFIX = {}
def read(fname):
return open(os.path.join(os.path.dirname(__file__), fname)).read()
def get_require_version(name):
if minor_version % 2:
require = '%s >= %s.%s.dev0, < %s.%s'
else:
require = '%s >= %s.%s, < %s.%s'
require %= (name, major_version, minor_version,
major_version, minor_version + 1)
return require
config = ConfigParser.ConfigParser()
config.readfp(open('tryton.cfg'))
info = dict(config.items('tryton'))
for key in ('depends', 'extras_depend', 'xml'):
if key in info:
info[key] = info[key].strip().splitlines()
version = info.get('version', '0.0.1')
major_version, minor_version, _ = version.split('.', 2)
major_version = int(major_version)
minor_version = int(minor_version)
requires = []
for dep in info.get('depends', []):
if not re.match(r'(ir|res|webdav)(\W|$)', dep):
prefix = MODULE2PREFIX.get(dep, 'trytond')
requires.append('%s_%s >= %s.%s, < %s.%s' %
(prefix, dep, major_version, minor_version,
major_version, minor_version + 1))
requires.append(get_require_version('trytond'))
tests_require = [get_require_version('proteus')]
setup(name='%s_%s' % (PREFIX, MODULE),
version=version,
description='',
long_description=read('README'),
author='NaN·tic',
author_email='info@nan-tic.com',
url='http://www.nan-tic.com/',
download_url="https://bitbucket.org/nantic/trytond-%s" % MODULE,
package_dir={'trytond.modules.%s' % MODULE: '.'},
packages=[
'trytond.modules.%s' % MODULE,
'trytond.modules.%s.tests' % MODULE,
],
package_data={
'trytond.modules.%s' % MODULE: (info.get('xml', [])
+ ['tryton.cfg', 'locale/*.po', 'tests/*.rst']),
},
classifiers=[
'Development Status :: 5 - Production/Stable',
'Environment :: Plugins',
'Framework :: Tryton',
'Intended Audience :: Developers',
'Intended Audience :: Financial and Insurance Industry',
'Intended Audience :: Legal Industry',
'License :: OSI Approved :: GNU General Public License (GPL)',
'Natural Language :: Bulgarian',
'Natural Language :: Catalan',
'Natural Language :: Czech',
'Natural Language :: Dutch',
'Natural Language :: English',
'Natural Language :: French',
'Natural Language :: German',
'Natural Language :: Russian',
'Natural Language :: Spanish',
'Operating System :: OS Independent',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Topic :: Office/Business',
],
license='GPL-3',
install_requires=requires,
zip_safe=False,
entry_points="""
[trytond.modules]
%s = trytond.modules.%s
""" % (MODULE, MODULE),
test_suite='tests',
test_loader='trytond.test_loader:Loader',
tests_require=tests_require,
)

3
tests/__init__.py Normal file
View File

@ -0,0 +1,3 @@
# The COPYRIGHT file at the top level of this repository contains the full
# copyright notices and license terms.
from .test_production_split_serial_number import suite

View File

@ -0,0 +1,178 @@
=======================================
Production Split Serial Number Scenario
=======================================
Imports::
>>> import datetime
>>> from dateutil.relativedelta import relativedelta
>>> from decimal import Decimal
>>> from proteus import config, Model, Wizard
>>> today = datetime.date.today()
>>> yesterday = today - relativedelta(days=1)
Create database::
>>> config = config.set_trytond()
>>> config.pool.test = True
Install production Module::
>>> Module = Model.get('ir.module.module')
>>> module, = Module.find([
... ('name', '=', 'production_split_serial_number')])
>>> module.click('install')
>>> Wizard('ir.module.module.install_upgrade').execute('upgrade')
Create company::
>>> Currency = Model.get('currency.currency')
>>> CurrencyRate = Model.get('currency.currency.rate')
>>> Company = Model.get('company.company')
>>> Party = Model.get('party.party')
>>> company_config = Wizard('company.company.config')
>>> company_config.execute('company')
>>> company = company_config.form
>>> party = Party(name='Dunder Mifflin')
>>> party.save()
>>> company.party = party
>>> currencies = Currency.find([('code', '=', 'USD')])
>>> if not currencies:
... currency = Currency(name='Euro', symbol=u'$', code='USD',
... rounding=Decimal('0.01'), mon_grouping='[3, 3, 0]',
... mon_decimal_point=',')
... currency.save()
... CurrencyRate(date=today + relativedelta(month=1, day=1),
... rate=Decimal('1.0'), currency=currency).save()
... else:
... currency, = currencies
>>> company.currency = currency
>>> company_config.execute('add')
>>> company, = Company.find()
Reload the context::
>>> User = Model.get('res.user')
>>> config._context = User.get_preferences(True, config.context)
Create product::
>>> Sequence = Model.get('ir.sequence')
>>> ProductUom = Model.get('product.uom')
>>> unit, = ProductUom.find([('name', '=', 'Unit')])
>>> ProductTemplate = Model.get('product.template')
>>> Product = Model.get('product.product')
>>> product = Product()
>>> template = ProductTemplate()
>>> template.name = 'product'
>>> template.default_uom = unit
>>> template.type = 'goods'
>>> template.list_price = Decimal(30)
>>> template.cost_price = Decimal(20)
>>> template.serial_number = True
>>> product_sequence = Sequence(code='stock.lot', name='Product Sequence')
>>> product_sequence.save()
>>> template.lot_sequence = product_sequence
>>> template.save()
>>> product.template = template
>>> product.save()
Create Components::
>>> component1 = Product()
>>> template1 = ProductTemplate()
>>> template1.name = 'component 1'
>>> template1.default_uom = unit
>>> template1.type = 'goods'
>>> template1.list_price = Decimal(5)
>>> template1.cost_price = Decimal(1)
>>> template1.save()
>>> component1.template = template1
>>> component1.save()
>>> meter, = ProductUom.find([('name', '=', 'Meter')])
>>> centimeter, = ProductUom.find([('name', '=', 'centimeter')])
>>> component2 = Product()
>>> template2 = ProductTemplate()
>>> template2.name = 'component 2'
>>> template2.default_uom = meter
>>> template2.type = 'goods'
>>> template2.list_price = Decimal(7)
>>> template2.cost_price = Decimal(5)
>>> template2.save()
>>> component2.template = template2
>>> component2.save()
Create Bill of Material::
>>> BOM = Model.get('production.bom')
>>> BOMInput = Model.get('production.bom.input')
>>> BOMOutput = Model.get('production.bom.output')
>>> bom = BOM(name='product')
>>> input1 = BOMInput()
>>> bom.inputs.append(input1)
>>> input1.product = component1
>>> input1.quantity = 5
>>> input2 = BOMInput()
>>> bom.inputs.append(input2)
>>> input2.product = component2
>>> input2.quantity = 150
>>> input2.uom = centimeter
>>> output = BOMOutput()
>>> bom.outputs.append(output)
>>> output.product = product
>>> output.quantity = 1
>>> bom.save()
>>> ProductBom = Model.get('product.product-production.bom')
>>> product.boms.append(ProductBom(bom=bom))
>>> product.save()
Create an Inventory::
>>> Inventory = Model.get('stock.inventory')
>>> InventoryLine = Model.get('stock.inventory.line')
>>> Location = Model.get('stock.location')
>>> storage, = Location.find([
... ('code', '=', 'STO'),
... ])
>>> inventory = Inventory()
>>> inventory.location = storage
>>> inventory_line1 = InventoryLine()
>>> inventory.lines.append(inventory_line1)
>>> inventory_line1.product = component1
>>> inventory_line1.quantity = 200
>>> inventory_line2 = InventoryLine()
>>> inventory.lines.append(inventory_line2)
>>> inventory_line2.product = component2
>>> inventory_line2.quantity = 60
>>> inventory.save()
>>> Inventory.confirm([inventory.id], config.context)
>>> inventory.state
u'done'
Make a production::
>>> Production = Model.get('production')
>>> production = Production()
>>> production.product = product
>>> production.bom = bom
>>> production.quantity = 4
>>> production.save()
>>> split_production = Wizard('production.split', [production])
>>> split_production.form.quantity
1.0
>>> split_production.form.count = 2
>>> split_production.execute('split')
>>> productions = Production.find([])
>>> len(productions)
3
>>> lots = [o.lot for p in productions for o in p.outputs if o.lot]
>>> lot1, lot2 = sorted(lots, key=lambda a: int(a.number))
>>> lot1.number
u'1'
>>> lot2.number
u'2'
>>> product_sequence.reload()
>>> product_sequence.number_next
3

View File

@ -0,0 +1,187 @@
=======================================================
Production Split Serial With Output Lot Number Scenario
=======================================================
Imports::
>>> import datetime
>>> from dateutil.relativedelta import relativedelta
>>> from decimal import Decimal
>>> from proteus import config, Model, Wizard
>>> today = datetime.date.today()
>>> yesterday = today - relativedelta(days=1)
Create database::
>>> config = config.set_trytond()
>>> config.pool.test = True
Install production Module::
>>> Module = Model.get('ir.module.module')
>>> modules = Module.find([
... ('name', 'in', ['production_split_serial_number',
... 'production_output_lot'])])
>>> Module.install([m.id for m in modules], config.context)
>>> Wizard('ir.module.module.install_upgrade').execute('upgrade')
Create company::
>>> Currency = Model.get('currency.currency')
>>> CurrencyRate = Model.get('currency.currency.rate')
>>> Company = Model.get('company.company')
>>> Party = Model.get('party.party')
>>> company_config = Wizard('company.company.config')
>>> company_config.execute('company')
>>> company = company_config.form
>>> party = Party(name='Dunder Mifflin')
>>> party.save()
>>> company.party = party
>>> currencies = Currency.find([('code', '=', 'USD')])
>>> if not currencies:
... currency = Currency(name='Euro', symbol=u'$', code='USD',
... rounding=Decimal('0.01'), mon_grouping='[3, 3, 0]',
... mon_decimal_point=',')
... currency.save()
... CurrencyRate(date=today + relativedelta(month=1, day=1),
... rate=Decimal('1.0'), currency=currency).save()
... else:
... currency, = currencies
>>> company.currency = currency
>>> company_config.execute('add')
>>> company, = Company.find()
Reload the context::
>>> User = Model.get('res.user')
>>> config._context = User.get_preferences(True, config.context)
Create product::
>>> ProductUom = Model.get('product.uom')
>>> tType = Model.get('stock.lot.type')
>>> unit, = ProductUom.find([('name', '=', 'Unit')])
>>> ProductTemplate = Model.get('product.template')
>>> Product = Model.get('product.product')
>>> product = Product()
>>> template = ProductTemplate()
>>> template.name = 'product'
>>> template.default_uom = unit
>>> template.type = 'goods'
>>> template.list_price = Decimal(30)
>>> template.cost_price = Decimal(20)
>>> template.serial_number = True
>>> template.save()
>>> product.template = template
>>> product.save()
Create Components::
>>> component1 = Product()
>>> template1 = ProductTemplate()
>>> template1.name = 'component 1'
>>> template1.default_uom = unit
>>> template1.type = 'goods'
>>> template1.list_price = Decimal(5)
>>> template1.cost_price = Decimal(1)
>>> template1.save()
>>> component1.template = template1
>>> component1.save()
>>> meter, = ProductUom.find([('name', '=', 'Meter')])
>>> centimeter, = ProductUom.find([('name', '=', 'centimeter')])
>>> component2 = Product()
>>> template2 = ProductTemplate()
>>> template2.name = 'component 2'
>>> template2.default_uom = meter
>>> template2.type = 'goods'
>>> template2.list_price = Decimal(7)
>>> template2.cost_price = Decimal(5)
>>> template2.save()
>>> component2.template = template2
>>> component2.save()
Create Bill of Material::
>>> BOM = Model.get('production.bom')
>>> BOMInput = Model.get('production.bom.input')
>>> BOMOutput = Model.get('production.bom.output')
>>> bom = BOM(name='product')
>>> input1 = BOMInput()
>>> bom.inputs.append(input1)
>>> input1.product = component1
>>> input1.quantity = 5
>>> input2 = BOMInput()
>>> bom.inputs.append(input2)
>>> input2.product = component2
>>> input2.quantity = 150
>>> input2.uom = centimeter
>>> output = BOMOutput()
>>> bom.outputs.append(output)
>>> output.product = product
>>> output.quantity = 1
>>> bom.save()
>>> ProductBom = Model.get('product.product-production.bom')
>>> product.boms.append(ProductBom(bom=bom))
>>> product.save()
Create an Inventory::
>>> Inventory = Model.get('stock.inventory')
>>> InventoryLine = Model.get('stock.inventory.line')
>>> Location = Model.get('stock.location')
>>> storage, = Location.find([
... ('code', '=', 'STO'),
... ])
>>> inventory = Inventory()
>>> inventory.location = storage
>>> inventory_line1 = InventoryLine()
>>> inventory.lines.append(inventory_line1)
>>> inventory_line1.product = component1
>>> inventory_line1.quantity = 200
>>> inventory_line2 = InventoryLine()
>>> inventory.lines.append(inventory_line2)
>>> inventory_line2.product = component2
>>> inventory_line2.quantity = 60
>>> inventory.save()
>>> Inventory.confirm([inventory.id], config.context)
>>> inventory.state
u'done'
Configure production sequence::
>>> Sequence = Model.get('ir.sequence')
>>> Config = Model.get('production.configuration')
>>> config = Config()
>>> config.output_lot_creation = 'done'
>>> output_sequence = Sequence(code='stock.lot', name='Output Sequence')
>>> output_sequence.save()
>>> config.output_lot_sequence = output_sequence
>>> config.save()
Make a production::
>>> Production = Model.get('production')
>>> production = Production()
>>> production.product = product
>>> production.bom = bom
>>> production.quantity = 4
>>> production.save()
>>> split_production = Wizard('production.split', [production])
>>> split_production.form.quantity
1.0
>>> split_production.form.count = 2
>>> split_production.execute('split')
>>> productions = Production.find([])
>>> len(productions)
3
>>> lots = [o.lot for p in productions for o in p.outputs if o.lot]
>>> lot1, lot2 = sorted(lots, key=lambda a: int(a.number))
>>> lot1.number
u'1'
>>> lot2.number
u'2'
>>> output_sequence.reload()
>>> output_sequence.number_next
3

View File

@ -0,0 +1,33 @@
# The COPYRIGHT file at the top level of this repository contains the full
# copyright notices and license terms.
import unittest
import doctest
import trytond.tests.test_tryton
from trytond.tests.test_tryton import test_depends
from trytond.tests.test_tryton import doctest_setup, doctest_teardown
class TestCase(unittest.TestCase):
'Test module'
def setUp(self):
trytond.tests.test_tryton.install_module(
'production_split_serial_number')
def test0006depends(self):
'Test depends'
test_depends()
def suite():
suite = trytond.tests.test_tryton.suite()
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestCase))
suite.addTests(doctest.DocFileSuite(
'scenario_production_split_serial_number.rst',
setUp=doctest_setup, tearDown=doctest_teardown, encoding='utf-8',
optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
suite.addTests(doctest.DocFileSuite(
'scenario_production_split_serial_number_output_lot.rst',
setUp=doctest_setup, tearDown=doctest_teardown, encoding='utf-8',
optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
return suite

9
tryton.cfg Normal file
View File

@ -0,0 +1,9 @@
[tryton]
version=3.4.0
depends:
stock_serial_number
stock_lot_sequence
production_split
extras_depend:
production_output_lot
xml: