Initial import from Docker volume

This commit is contained in:
root
2025-12-26 13:11:43 +00:00
commit 4998dc066a
13336 changed files with 1767801 additions and 0 deletions

22
modules/lot/__init__.py Executable file
View File

@@ -0,0 +1,22 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from trytond.pool import Pool
from . import lot
def register():
Pool.register(
lot.Lot,
lot.QtHist,
lot.QtType,
lot.LotSplitMerge,
lot.SplitWizardStart,
lot.MergeLotsStart,
lot.SplitLine,
module='lot', type_='model')
Pool.register(
lot.SplitWizard,
lot.MergeLots,
module='lot', type_='wizard')

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,14 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from trytond.pool import Pool
from . import lot
def register():
Pool.register(
lot.Lot,
lot.QtHist,
lot.QtType,
module='lot', type_='model')

View File

@@ -0,0 +1,286 @@
# This file is part of Tryton. 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 Bool, Eval, Id
from trytond.model import (ModelSQL, ModelView)
from decimal import getcontext, Decimal, ROUND_HALF_UP
from sql.aggregate import Count, Max, Min, Sum, Avg, BoolOr
from sql import Column, Literal
from sql.functions import CurrentTimestamp, DateTrunc
import datetime
import logging
logger = logging.getLogger(__name__)
class Lot(ModelSQL, ModelView):
"Lot"
__name__ = 'lot.lot'
_rec_name = 'lot_name'
line = fields.Many2One(
'purchase.line', "Line", ondelete='CASCADE',
)
sale_line = fields.Many2One(
'sale.line', "Line", ondelete='CASCADE',
)
lot_name = fields.Char("Lot")
lot_qt = fields.Integer("Quantity",states={
'readonly': Eval('lot_himself', True),
},)
lot_unit = fields.Many2One('product.uom', "Unit",required=True)
lot_product = fields.Many2One('product.product', "Product")
lot_type = fields.Selection([
(None, ''),
('delivery', 'Delivery'),
('loss', 'Loss'),
('receive', 'Receive')
], 'Lot type',required=True)
lot_status = fields.Selection([
(None, ''),
('forecast', 'Forecast'),
('nom', 'Nom'),
('adjusted', 'Adjusted'),
('trade', 'Trade')
], 'Lot status',required=True)
lot_quantity = fields.Numeric("Net Qt",digits='lot_unit',required=True)
lot_unit_line = fields.Function(fields.Many2One('product.uom', "Unit",required=True),'get_unit')
lot_premium = fields.Numeric("Prem/Disc", digits=(1,2))
lot_premium_sale = fields.Numeric("Prem/Disc", digits=(1,2))
lot_shipment = fields.Many2One('stock.shipment.in', "Shipment")
lot_gross_quantity = fields.Numeric("Gross Qt",digits='lot_unit')
lot_parent = fields.Many2One('purchase.lot',"Parent")
lot_childs = fields.One2Many('purchase.lot','lot_parent',"Childs")
lot_himself = fields.Many2One('purchase.lot',"Lot")
lot_container = fields.Char("Container")
lot_qt_hist = fields.One2Many('purchase.lot.qt','lot',"Qt history")
lot_unit_line = fields.Function(fields.Many2One('product.uom', "Unit"),'get_unit')
lot_price = fields.Function(fields.Numeric("Price", digits=(1,2)),'get_lot_price')
lot_price_sale = fields.Function(fields.Numeric("Price", digits=(1,2)),'get_lot_sale_price')
# lot_tu_net_qt = fields.Numeric("Net Takeup Qt")
# lot_tu_gross_qt = fields.Numeric("Gross Takeup Qt")
# lot_wr_net_qt = fields.Numeric("Net WR Qt")
# lot_wr_gross_qt = fields.Numeric("Gross WR Qt")
# lot_lr_net_qt = fields.Numeric("Net LR Qt")
# lot_lr_gross_qt = fields.Numeric("Gross LR Qt")
@classmethod
def __setup__(cls):
super(Lot, cls).__setup__()
cls._order.insert(0, ('lot_shipment', 'ASC'))
@classmethod
def default_lot_unit(cls):
return 2 #kg
@classmethod
def default_lot_qt(cls):
return Decimal(0)
@classmethod
def default_lot_gross_quantity(cls):
return Decimal(0)
@classmethod
def default_lot_status(cls):
return 'trade'
@classmethod
def default_lot_type(cls):
return 'receive'
def get_unit(self,name):
return 2 #kg
def get_lot_price(self,name=None):
price = Decimal(0)
if self.line:
l = Pool().get('purchase.line')(self.line)
premium = Decimal(0)
if self.lot_premium:
premium = self.lot_premium
price = premium + l.unit_price
return round(price,2)
def get_lot_sale_price(self,name=None):
price = Decimal(0)
# if self.sale_line:
# l = Pool().get('sale.line')(self.sale_line)
# premium = Decimal(0)
# if self.lot_premium_sale:
# premium = self.lot_premium_sale
# price = premium + l.unit_price
return round(price,2)
def get_sale_amount(self,name):
round_context = getcontext()
round_context.rounding = ROUND_HALF_UP
price = self.get_lot_sale_price()
# if price and not self.lot_wr_net_qt:
# return round(price * self.lot_quantity * Decimal(2.2046),2)
# elif price and self.lot_wr_net_qt:
# return round(price * self.lot_wr_net_qt * Decimal(2.2046),2)
return None
def get_amount(self,name):
round_context = getcontext()
round_context.rounding = ROUND_HALF_UP
price = self.get_lot_price()
# if price and not self.lot_wr_net_qt:
# return round(price * self.lot_quantity * Decimal(2.2046),2)
# elif price and self.lot_wr_net_qt:
# return round(price * self.lot_wr_net_qt * Decimal(2.2046),2)
return None
# @classmethod
# def write(cls, *args):
# super().write(*args)
# lots = sum(args[::2], [])
# to_write = []
# for l in lots:
# lotnetqt = Decimal(0)
# lotgrossqt = Decimal(0)
# if l.lot_lr_net_qt and l.lot_lr_net_qt > 0 and l.lot_lr_net_qt != l.lot_quantity:
# lotnetqt = l.lot_lr_net_qt
# lotgrossqt = l.lot_lr_gross_qt
# elif l.lot_wr_net_qt and not l.lot_lr_net_qt and l.lot_wr_net_qt > 0 and l.lot_wr_net_qt != l.lot_quantity:
# lotnetqt = l.lot_wr_net_qt
# lotgrossqt = l.lot_wr_gross_qt
# elif l.lot_tu_net_qt and not l.lot_lr_net_qt and not l.lot_wr_net_qt and l.lot_tu_net_qt > 0 and l.lot_tu_net_qt != l.lot_quantity:
# lotnetqt = l.lot_tu_net_qt
# lotgrossqt = l.lot_tu_gross_qt
# if lotnetqt:
# to_write.extend(([l], {
# 'lot_quantity': lotnetqt,
# 'lot_final_quantity': lotgrossqt,
# }))
# if to_write:
# cls.write(*to_write)
# @classmethod
# def create(cls, vlist):
# pool = Pool()
# vlist = [x.copy() for x in vlist]
# for values in vlist:
# parent = values.get('lot_parent')
# if parent:
# L = pool.get('position.lot')
# l = L(parent)
# balenw = round(Decimal((l.lot_quantity if l.lot_quantity else 0)/l.lot_bale),2)
# balegw = round(Decimal((l.lot_final_quantity if l.lot_final_quantity else 0)/l.lot_bale),2)
# balenwtu = round(Decimal((l.lot_tu_net_qt if l.lot_tu_net_qt else 0)/l.lot_bale),2)
# balegwtu = round(Decimal((l.lot_tu_gross_qt if l.lot_tu_gross_qt else 0)/l.lot_bale),2)
# balenwwr = round(Decimal((l.lot_wr_net_qt if l.lot_wr_net_qt else 0)/l.lot_bale),2)
# balegwwr = round(Decimal((l.lot_wr_gross_qt if l.lot_wr_gross_qt else 0)/l.lot_bale),2)
# balenwlr = round(Decimal((l.lot_lr_net_qt if l.lot_lr_net_qt else 0)/l.lot_bale),2)
# balegwlr = round(Decimal((l.lot_lr_gross_qt if l.lot_lr_gross_qt else 0)/l.lot_bale),2)
# nw = Decimal(int(values.get('lot_bale')) * balenw)
# gw = Decimal(int(values.get('lot_bale')) * balegw)
# nwtu = Decimal(int(values.get('lot_bale')) * balenwtu)
# gwtu = Decimal(int(values.get('lot_bale')) * balegwtu)
# nwwr = Decimal(int(values.get('lot_bale')) * balenwwr)
# gwwr = Decimal(int(values.get('lot_bale')) * balegwwr)
# nwlr = Decimal(int(values.get('lot_bale')) * balenwlr)
# gwlr = Decimal(int(values.get('lot_bale')) * balegwlr)
# l.lot_bale -= int(values.get('lot_bale'))
# if l.lot_quantity:
# l.lot_quantity -= nw
# if l.lot_final_quantity:
# l.lot_final_quantity -= gw
# if l.lot_tu_net_qt:
# l.lot_tu_net_qt -= nwtu
# if l.lot_tu_gross_qt:
# l.lot_tu_gross_qt -= gwtu
# if l.lot_wr_net_qt:
# l.lot_wr_net_qt -= nwwr
# if l.lot_wr_gross_qt:
# l.lot_wr_gross_qt -= gwwr
# if l.lot_lr_net_qt:
# l.lot_lr_net_qt -= nwlr
# if l.lot_lr_gross_qt:
# l.lot_lr_gross_qt -= gwlr
# values['lot_quantity'] = nw
# values['lot_final_quantity'] = gw
# values['lot_tu_net_qt'] = nwtu
# values['lot_tu_gross_qt'] = gwtu
# values['lot_wr_net_qt'] = nwwr
# values['lot_wr_gross_qt'] = gwwr
# values['lot_lr_net_qt'] = nwlr
# values['lot_lr_gross_qt'] = gwlr
# L.save([l])
# return super(Lot, cls).create(vlist)
# @classmethod
# def validate(cls, lots):
# super(Lot, cls).validate(lots)
# pool = Pool()
# Valuation = pool.get('valuation.valuation')
# Position = pool.get('trade.position')
# for lot in lots:
# L = pool.get('position.lot')
# if not lot.lot_himself:
# l = L(lot.id)
# l.lot_himself = lot.id
# L.save([l])
# if lot.lot_parent:
# pa = L(lot.lot_parent)
# l = L(lot.id)
# l.position = pa.position
# l.lot_quality = pa.lot_quality
# l.lot_unit = pa.lot_unit
# L.save([l])
# @classmethod
# def copy(cls, positions, default=None):
# if default is None:
# default = {}
# else:
# default = default.copy()
# return super(Lot, cls).copy(lots, default=default)
# @classmethod
# def delete(cls, lots):
# positions = []
# pool = Pool()
# LL = pool.get('position.lot')
# for lot in lots:
# positions.append(lot.position)
# if lot.lot_parent:
# pa = LL(lot.lot_parent)
# if pa:
# pa.lot_bale += lot.lot_bale
# pa.lot_quantity += lot.lot_quantity
# pa.lot_final_quantity += lot.lot_final_quantity
# if pa.lot_tu_net_qt:
# pa.lot_tu_net_qt += lot.lot_tu_net_qt
# pa.lot_tu_gross_qt += lot.lot_tu_gross_qt
# if pa.lot_wr_net_qt:
# pa.lot_wr_net_qt += lot.lot_wr_net_qt
# pa.lot_wr_gross_qt += lot.lot_wr_gross_qt
# if pa.lot_lr_net_qt:
# pa.lot_lr_net_qt += lot.lot_lr_net_qt
# pa.lot_lr_gross_qt += lot.lot_lr_gross_qt
# LL.save([pa])
# super(Lot, cls).delete(lots)
class QtType(ModelSQL, ModelView):
"Type"
__name__ = 'lot.qt.type'
name = fields.Char("Name")
class QtHist(ModelSQL, ModelView):
"Quantities"
__name__ = 'lot.qt'
lot = fields.Many2One(
'lot.lot', "Lot", ondelete='CASCADE',
)
quantity_type = fields.Many2One('lot.qt.type',"Type")
quantity = fields.Numeric("Quantity")

View File

@@ -0,0 +1,57 @@
<tryton>
<data>
<record model="ir.ui.view" id="lot_view_tree">
<field name="model">lot.lot</field>
<field name="type">tree</field>
<field name="priority" eval="10"/>
<field name="name">lot_tree</field>
</record>
<record model="ir.ui.view" id="lot_view_tree_sequence">
<field name="model">lot.lot</field>
<field name="type">tree</field>
<field name="priority" eval="20"/>
<field name="name">lot_tree_sequence</field>
</record>
<record model="ir.ui.view" id="lot_view_tree_sequence2">
<field name="model">lot.lot</field>
<field name="type">tree</field>
<field name="priority" eval="20"/>
<field name="name">lot_tree_sequence2</field>
</record>
<record model="ir.ui.view" id="lot_view_tree_sequence3">
<field name="model">lot.lot</field>
<field name="type">tree</field>
<field name="priority" eval="20"/>
<field name="name">lot_tree_sequence3</field>
</record>
<record model="ir.ui.view" id="lot_view_form">
<field name="model">lot.lot</field>
<field name="type">form</field>
<field name="name">lot_form</field>
</record>
<record model="ir.ui.view" id="lot_qt_view_tree">
<field name="model">lot.qt</field>
<field name="type">tree</field>
<field name="priority" eval="10"/>
<field name="name">lot_qt_tree</field>
</record>
<record model="ir.ui.view" id="lot_qt_view_tree_sequence">
<field name="model">lot.qt</field>
<field name="type">tree</field>
<field name="priority" eval="20"/>
<field name="name">lot_qt_tree_sequence</field>
</record>
<record model="ir.ui.view" id="lot_qt_type_view_tree">
<field name="model">lot.qt.type</field>
<field name="type">tree</field>
<field name="priority" eval="10"/>
<field name="name">lot_qt_type_tree</field>
</record>
<record model="ir.ui.view" id="lot_qt_type_view_tree_sequence">
<field name="model">lot.qt.type</field>
<field name="type">tree</field>
<field name="priority" eval="20"/>
<field name="name">lot_qt_type_tree_sequence</field>
</record>
</data>
</tryton>

View File

@@ -0,0 +1,9 @@
[tryton]
version=7.2.7
depends:
ir
purchase
sale
res
xml:
lot.xml

View File

@@ -0,0 +1,26 @@
<?xml version="1.0"?>
<form col="4">
<label name="lot_name"/>
<field name="lot_name"/>
<label name="lot_parent"/>
<field name="lot_parent"/>
<label name="lot_qt"/>
<field name="lot_qt"/>
<label name="lot_product"/>
<field name="lot_product"/>
<label name="lot_type"/>
<field name="lot_type"/>
<label name="lot_status"/>
<field name="lot_status"/>
<label name="lot_shipment"/>
<field name="lot_shipment"/>
<label name="lot_quantity"/>
<field name="lot_quantity"/>
<label name="lot_gross_quantity"/>
<field name="lot_gross_quantity"/>
<newline/>
<label name="lot_unit"/>
<field name="lot_unit"/>
<newline/>
<field name="lot_childs" colspan="4" mode="tree,form" view_ids="purchase.lot_view_tree_sequence2,purchase.lot_view_form"/>
</form>

View File

@@ -0,0 +1,4 @@
<tree>
<field name="quantity_type"/>
<field name="quantity"/>
</tree>

View File

@@ -0,0 +1,4 @@
<tree>
<field name="quantity_type"/>
<field name="quantity"/>
</tree>

View File

@@ -0,0 +1,3 @@
<tree>
<field name="name"/>
</tree>

View File

@@ -0,0 +1,3 @@
<tree>
<field name="name"/>
</tree>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0"?>
<tree>
<field name="lot_himself"/>
<field name="line"/>
<field name="lot_type"/>
<field name="lot_status"/>
<field name="lot_shipment"/>
<field name="lot_quantity"/>
<field name="lot_unit"/>
<field name="lot_gross_quantity"/>
<field name="lot_parent"/>
</tree>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0"?>
<tree sequence="sequence" editable="1">
<field name="lot_himself" width="80"/>
<field name="lot_qt" width="80"/>
<field name="lot_product" width="80"/>
<field name="lot_type" width="80" optional="1"/>
<field name="lot_status" width="80" optional="1"/>
<field name="lot_shipment" width="120"/>
<field name="lot_quantity" width="80"/>
<field name="lot_unit" width="80"/>
<field name="lot_gross_quantity" width="80"/>
<field name="lot_premium" width="80"/>
<field name="lot_price" width="80"/>
<field name="lot_type" width="80"/>
<field name="lot_status" width="80"/>
</tree>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0"?>
<tree sequence="sequence" editable="1">
<field name="lot_name" width="80"/>
<field name="lot_qt" width="80"/>
<field name="lot_quantity" width="80"/>
<field name="lot_unit" width="80"/>
<field name="lot_gross_quantity" width="80"/>
</tree>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0"?>
<tree sequence="sequence" editable="1">
<field name="lot_himself" width="80"/>
<field name="lot_qt" width="80"/>
<field name="lot_product" width="80"/>
<field name="lot_type" width="80" optional="1"/>
<field name="lot_status" width="80" optional="1"/>
<field name="lot_shipment" width="120"/>
<field name="lot_quantity" width="80"/>
<field name="lot_unit" width="80"/>
<field name="lot_gross_quantity" width="80"/>
<field name="lot_premium_sale" width="80"/>
<field name="lot_price_sale" width="80"/>
</tree>

BIN
modules/lot/dist/trytond_lot-7.2.7-py3.11.egg vendored Executable file

Binary file not shown.

805
modules/lot/lot.py Executable file
View File

@@ -0,0 +1,805 @@
# This file is part of Tryton. 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 Bool, Eval, Id
from trytond.model import (ModelSQL, ModelView)
from trytond.exceptions import UserWarning, UserError
from trytond.transaction import Transaction, inactive_records
from trytond.wizard import Button, StateTransition, StateView, Wizard, StateAction
from decimal import getcontext, Decimal, ROUND_HALF_UP
from sql.aggregate import Count, Max, Min, Sum, Avg, BoolOr
from sql import Column, Literal
from sql.functions import CurrentTimestamp, DateTrunc
import datetime
import logging
import json
logger = logging.getLogger(__name__)
class Lot(ModelSQL, ModelView):
"Lot"
__name__ = 'lot.lot'
_rec_name = 'lot_name'
lot_name = fields.Char("Lot")
number = fields.Char("Number", readonly=True)
lot_qt = fields.Float("Quantity",required=False)
lot_unit = fields.Many2One('product.uom', "Unit",required=False)
lot_product = fields.Many2One('product.product', "Product")
lot_type = fields.Selection([
('virtual', 'Open'),
('physic', 'Physic'),
('loss', 'Loss'),
], 'Qt type')
lot_status = fields.Selection([
('forecast', 'Forecast'),
('loading', 'Loading'),
('transit', 'Transit'),
('destination', 'Destination'),
('stock', 'Stock'),
('delivered', 'Delivered')
], 'Where')
lot_av = fields.Selection([
('available', 'Available'),
('reserved', 'Reserved'),
('locked', 'Locked'),
('prov', 'Prov. inv'),
('invoiced', 'Invoiced')
], 'State')
lot_quantity = fields.Function(fields.Numeric("Net weight",digits='line_unit'),'get_current_quantity')
lot_premium = fields.Numeric("Premium", digits='line_unit')
lot_premium_sup = fields.Numeric("Sup prem", digits='line_unit')
lot_premium_tpl = fields.Numeric("Tpl prem", digits='line_unit')
lot_premium_sale = fields.Numeric("Prem/Disc", digits='line_unit')
lot_shipment_in = fields.Many2One('stock.shipment.in', "Shipment In")
lot_shipment_out = fields.Many2One('stock.shipment.out', "Shipment Out")
lot_shipment_internal = fields.Many2One('stock.shipment.internal', "Shipment Internal")
lot_gross_quantity = fields.Function(fields.Numeric("Gross weight",digits='line_unit'),'get_current_gross_quantity')
lot_parent = fields.Many2One('lot.lot',"Parent")
lot_childs = fields.One2Many('lot.lot','lot_parent',"Childs")
lot_himself = fields.Many2One('lot.lot',"Lot")
lot_container = fields.Char("Container")
lot_unit_line = fields.Many2One('product.uom', "Unit",required=True)
lot_price = fields.Function(fields.Numeric("Price", digits='line_unit'),'get_lot_price')
lot_price_sale = fields.Function(fields.Numeric("Price", digits='line_unit'),'get_lot_sale_price')
lot_state = fields.Many2One('lot.qt.type',"Qt state")
lot_hist = fields.One2Many('lot.qt.hist','lot',"Qt hist")
lot_pur_inv_state = fields.Selection([
(None, ''),
('prov', 'Prov'),
('invoiced', 'Final')
], 'Pur. inv')
lot_sale_inv_state = fields.Selection([
(None, ''),
('prov', 'Prov'),
('invoiced', 'Final')
], 'Sale inv')
lot_price_ct_symbol = fields.Function(fields.Char(""),'get_price_ct_symbol')
lot_price_ct_symbol_sale = fields.Function(fields.Char(""),'get_price_ct_symbol_sale')
lot_price_ct_symbol_premium = fields.Function(fields.Char(""),'get_price_ct_symbol_premium')
line_unit = fields.Function(fields.Many2One('product.uom',"Line unit"),'get_unit_line')
lot_shipment_origin = fields.Function(
fields.Reference(
selection=[
("stock.shipment.in", "In"),
("stock.shipment.out", "Out"),
("stock.shipment.internal", "Internal"),
],
string="Shipment",
),
"get_shipment_origin",
)
lot_role = fields.Selection([
('normal', 'Normal'),
('technical', 'Technical'),
], 'Role', required=True)
split_operations = fields.One2Many(
'lot.split.merge', 'source_lot', 'Split/Merge Ops'
)
split_graph = fields.Function(
fields.Text('Split Graph'),
'get_split_graph'
)
@classmethod
def default_lot_role(cls):
return 'normal'
# ---------------------------------------------------------------------
# Technical helpers
# ---------------------------------------------------------------------
def _check_split_allowed(self):
if self.lot_role != 'normal':
raise UserError('Only normal lots can be split.')
if self.lot_type != 'physic':
raise UserError('Only physical lots can be split.')
if self.IsDelivered():
raise UserError('Delivered lots cannot be split.')
def _create_technical_parent(self):
Lot = Pool().get('lot.lot')
parent = Lot(
lot_name=f'TECH-{self.lot_name}',
lot_role='technical',
lot_type='virtual',
lot_product=self.lot_product,
lot_status=self.lot_status,
lot_av='locked',
)
Lot.save([parent])
return parent
def _clone_hist_with_ratio(self, ratio):
LotQtHist = Pool().get('lot.qt.hist')
hist = []
for h in self.lot_hist:
hist.append(
LotQtHist(
quantity_type=h.quantity_type,
quantity=h.quantity * ratio,
gross_quantity=h.gross_quantity * ratio,
)
)
return hist
def split_by_weight(self, splits):
Date = Pool().get('ir.date')
self._check_split_allowed()
total_weight = sum(s['quantity'] for s in splits)
lot_weight = self.get_current_quantity()
diff = (lot_weight - total_weight).quantize(Decimal('0.00001'))
if diff < 0:
raise UserError('Split weight exceeds lot weight.')
if diff != 0:
raise UserError(
f'Split does not fully consume the lot weight.\n'
f'Remaining: {diff}'
)
Lot = Pool().get('lot.lot')
Split = Pool().get('lot.split.merge')
children = []
# Parent becomes technical
self.lot_av = 'locked'
self.lot_role = 'technical'
self.lot_name = 'TECH-' + self.lot_name
line = self.line
self.line = None
Lot.save([self])
for s in splits:
ratio = s['quantity'] / lot_weight
lot_qt = None
if self.lot_qt:
lot_qt = (Decimal(self.lot_qt) * ratio).quantize(
Decimal('0.00001'), rounding=ROUND_HALF_UP
)
child = Lot(
lot_name=s.get('name'),
lot_role='normal',
lot_type='physic',
lot_qt=float(lot_qt) if lot_qt is not None else None,
lot_unit=self.lot_unit,
lot_product=self.lot_product,
lot_status=self.lot_status,
lot_av=self.lot_av,
lot_unit_line=self.lot_unit_line,
)
with Transaction().set_context(_skip_function_fields=True):
Lot.save([child])
child.set_current_quantity(s['quantity'], s['quantity'], 1)
child.lot_parent = self
child.line = line
Lot.save([child])
children.append(child)
ops = []
for c in children:
ops.append(
Split(
operation='split',
source_lot=self,
target_lot=c,
lot_qt=c.lot_qt,
quantity=c.get_current_quantity(),
operation_date=Date.today(),
)
)
Split.save(ops)
return children
@classmethod
def merge_lots(cls, lots):
Date = Pool().get('ir.date')
if len(lots) < 2:
raise UserError('At least two lots are required for merge.')
# parent = lots[0].lot_parent
# if not parent or parent.lot_role != 'technical':
# raise UserError('Lots must share a technical parent.')
product = lots[0].lot_product
for l in lots:
if l.lot_product != product:
raise UserError('Cannot merge lots of different products.')
Lot = Pool().get('lot.lot')
Split = Pool().get('lot.split.merge')
total_qt = sum([l.lot_qt or 0 for l in lots])
total_weight = sum([l.get_current_quantity() for l in lots])
ops = []
parents_name = ""
for l in lots:
l.lot_av = 'locked'
l.lot_role = 'technical'
l.line = None
parents_name += l.lot_name + "/"
ops.append(
Split(
operation='merge',
source_lot=l,
target_lot=merged,
lot_qt=l.lot_qt,
quantity=l.get_current_quantity(),
operation_date=Date.today(),
)
)
Lot.save(lots)
Split.save(ops)
merged = Lot(
lot_name='MERGE-' + parents_name,
lot_role='normal',
lot_type='physic',
lot_product=product,
lot_qt=total_qt,
lot_unit=lots[0].lot_unit,
lot_unit_line=lots[0].lot_unit_line,
)
merged.set_current_quantity(total_weight, total_weight, 1)
Lot.save([merged])
return merged
def get_split_graph(self, name=None):
Split = Pool().get('lot.split.merge')
nodes = {}
edges = []
def add_lot_node(lot):
if lot.id in nodes:
return
nodes[lot.id] = {
'id': f'lot.lot {lot.id}',
'label': f'{lot.lot_name}\n({lot.lot_role})',
'data_model': 'lot.lot',
'data_id': lot.id,
'shape': 'box' if lot.lot_role == 'technical' else 'ellipse',
'color': {
'background': '#FFD966' if lot.lot_role == 'technical' else '#A7C7E7',
'border': '#555',
},
}
def walk(lot):
add_lot_node(lot)
ops = Split.search([('source_lot', '=', lot.id)])
for op in ops:
tgt = op.target_lot
add_lot_node(tgt)
edges.append({
'from': f'lot.lot {lot.id}',
'to': f'lot.lot {tgt.id}',
'label': op.operation if op.operation else '',
'arrows': 'to',
'color': {
'color': '#267F82',
'highlight': '#1D5F62',
'hover': '#1D5F62'
}
})
walk(tgt)
walk(self)
# Calcul des relations parent-enfant
from collections import defaultdict
children_map = defaultdict(list)
parent_map = {}
for edge in edges:
from_id = edge['from']
to_id = edge['to']
children_map[from_id].append(to_id)
parent_map[to_id] = from_id
# Trouver la racine (nœud sans parent)
all_node_ids = {node['id'] for node in nodes.values()}
root_candidates = all_node_ids - set(parent_map.keys())
if root_candidates:
root_id = list(root_candidates)[0]
else:
root_id = f'lot.lot {self.id}'
# Assigner les niveaux hiérarchiques
levels = {}
def assign_levels(node_id, level=0):
if node_id in levels and levels[node_id] >= level:
return
levels[node_id] = level
for child in children_map.get(node_id, []):
assign_levels(child, level + 1)
assign_levels(root_id)
# Grouper les nœuds par niveau
by_level = defaultdict(list)
for node_id, level in levels.items():
by_level[level].append(node_id)
# Calculer les positions - IMPORTANT: utiliser des valeurs positives plus grandes
# Calculer les positions pour un graphe HORIZONTAL
X_SPACING = 180 # espace entre les niveaux (gauche → droite)
Y_SPACING = 100 # espace entre les nœuds dun même niveau
max_nodes_in_level = max(len(nodes) for nodes in by_level.values()) if by_level else 1
total_height = max_nodes_in_level * Y_SPACING
for level, node_ids in by_level.items():
level_height = len(node_ids) * Y_SPACING
start_y = (total_height - level_height) / 2 # Centrer verticalement
for i, node_id in enumerate(node_ids):
node_num = int(node_id.split()[-1])
# Niveau → X
x_pos = level * X_SPACING + 300
# Répartition verticale
y_pos = start_y + (i * Y_SPACING) + 100
nodes[node_num]['x'] = x_pos
nodes[node_num]['y'] = y_pos
nodes[node_num]['fixed'] = {
'x': True,
'y': True
}
# S'assurer que tous les nœuds ont des positions
for node in nodes.values():
if 'x' not in node:
node['x'] = 0
node['y'] = 0
node['fixed'] = {'x': True, 'y': True}
data = {
'nodes': list(nodes.values()),
'edges': edges,
'physics': {
'enabled': False # Désactivé car positions fixes
}
}
return 'visjson:' + json.dumps(data)
def get_shipment_origin(self, name):
if self.lot_shipment_in:
return 'stock.shipment.in,' + str(self.lot_shipment_in.id)
elif self.lot_shipment_out:
return 'stock.shipment.out,' + str(self.lot_shipment_out.id)
elif self.lot_shipment_internal:
return 'stock.shipment.internal,' + str(self.lot_shipment_internal.id)
return None
def get_unit_line(self,name):
if self.line:
return self.line.unit
return self.lot_unit_line
def get_lot_inv(self):
if self.invoice_line:
return self.invoice_line.invoice.origin
if self.invoice_line_prov:
return self.invoice_line_prov.invoice.origin
@classmethod
def __setup__(cls):
super(Lot, cls).__setup__()
#cls._order.insert(0, ('lot_shipment', 'ASC'))
@classmethod
def default_lot_qt(cls):
return Decimal(0)
@classmethod
def default_lot_av(cls):
return 'available'
@classmethod
def default_lot_status(cls):
return 'forecast'
@classmethod
def default_lot_type(cls):
return 'physic'
@classmethod
def default_lot_gross_quantity(cls):
return Decimal(0)
@classmethod
def default_lot_type(cls):
return 'receive'
@classmethod
def default_lot_state(cls):
LotQtType = Pool().get('lot.qt.type')
lqt = LotQtType.search([('sequence','=',1)])
if lqt:
return lqt[0].id
def get_price_ct_symbol(self,name):
if self.line:
return str(self.line.currency.symbol) + "/" + str(self.line.unit.symbol)
def get_price_ct_symbol_sale(self,name):
if self.sale_line:
return str(self.sale_line.currency.symbol) + "/" + str(self.sale_line.unit.symbol)
def get_price_ct_symbol_premium(self,name):
if self.line:
if self.line.enable_linked_currency:
return str(self.line.linked_currency.name) + "/" + str(self.line.linked_unit.symbol)
else:
return str(self.line.currency.symbol) + "/" + str(self.line.unit.symbol)
def get_hist_quantity(self,seq):
qt = Decimal(0)
gross_qt = Decimal(0)
if self.lot_state:
if self.lot_hist:
if seq != 0:
st = seq
else:
st = self.lot_state.id
lot = [e for e in self.lot_hist if e.quantity_type.id == st][0]
qt = round(lot.quantity,5)
gross_qt = round(lot.gross_quantity,5)
return qt, gross_qt
def get_virtual_diff(self):
Uom = Pool().get('product.uom')
line = self.line if self.line else self.sale_line
if line:
if line.lots:
physic_sum = Decimal(0)
for l in line.lots:
if l.lot_type == 'physic' :
physic_sum += round(Decimal(Uom.compute_qty(Uom(l.lot_unit_line),float(l.get_current_quantity()),l.line.unit)),5)
return line.quantity_theorical - physic_sum
def get_current_quantity(self,name=None):
# if self.lot_type == 'physic':
qt, gross_qt = self.get_hist_quantity(0)
return qt
# else:
# return self.get_virtual_diff()
def get_current_quantity_converted(self,name=None):
Uom = Pool().get('product.uom')
unit = self.line.unit if self.line else self.sale_line.unit
return round(Decimal(Uom.compute_qty(self.lot_unit_line, float(self.get_current_quantity()), unit)),5)
def get_current_gross_quantity(self,name=None):
if self.lot_type == 'physic':
qt, gross_qt = self.get_hist_quantity(0)
return gross_qt
else:
return None
def add_quantity_to_hist(self,net,gross,qt_type):
LotQtHist = Pool().get('lot.qt.hist')
lqh = LotQtHist()
lqh.quantity_type = qt_type
lqh.quantity = net
lqh.gross_quantity = gross
lqh.lot = self
return lqh
def set_current_quantity(self, net, gross, seq=1):
LotQtType = Pool().get('lot.qt.type')
lqtt = LotQtType.search([('sequence', '=', seq)])
if not lqtt:
return
lot_hist = list(getattr(self, 'lot_hist', []) or [])
existing = [e for e in lot_hist if e.quantity_type == lqtt[0]]
if existing:
hist = existing[0]
hist.quantity = net
hist.gross_quantity = gross
else:
lot_hist.append(self.add_quantity_to_hist(net, gross, lqtt[0]))
self.lot_hist = lot_hist
self.lot_state = lqtt[0]
def get_unit(self,name):
if self.line:
return self.line.unit
if self.sale_line:
return self.sale_line.unit
def get_lot_price(self,name=None):
price = Decimal(0)
if self.line:
if self.line.enable_linked_currency and self.line.linked_price and self.line.linked_currency and self.line.price_type == 'priced':
return self.line.get_price_linked_currency((self.lot_premium if self.lot_premium else 0))
else:
return self.line.get_price((self.lot_premium if self.lot_premium else 0))
def get_lot_sale_price(self,name=None):
price = Decimal(0)
if self.sale_line:
# if self.line.enable_linked_currency and self.line.linked_price and self.line.linked_currency and self.line.price_type == 'priced':
# return self.line.get_price_linked_currency((self.lot_premium if self.lot_premium else 0))
# else:
return self.sale_line.get_price((self.lot_premium if self.lot_premium else 0))
def get_sale_amount(self,name):
round_context = getcontext()
round_context.rounding = ROUND_HALF_UP
price = self.get_lot_sale_price()
return None
def get_amount(self,name):
round_context = getcontext()
round_context.rounding = ROUND_HALF_UP
price = self.get_lot_price()
return None
class QtType(ModelSQL, ModelView):
"Type"
__name__ = 'lot.qt.type'
name = fields.Char("Name")
sequence = fields.Integer("Sequence")
class QtHist(ModelSQL, ModelView):
"Quantities"
__name__ = 'lot.qt.hist'
lot = fields.Many2One(
'lot.lot', "Lot", ondelete='CASCADE',
)
quantity_type = fields.Many2One('lot.qt.type',"Type")
quantity = fields.Numeric("Net weight",digits=(1,5))
gross_quantity = fields.Numeric("Gross weight",digits=(1,5))
class LotSplitMerge(ModelSQL, ModelView):
"Lot Split Merge"
__name__ = 'lot.split.merge'
operation = fields.Selection([
('split', 'Split'),
('merge', 'Merge'),
], 'Operation', required=True)
source_lot = fields.Many2One(
'lot.lot', 'Source Lot', required=True, ondelete='CASCADE'
)
target_lot = fields.Many2One(
'lot.lot', 'Target Lot', required=True, ondelete='CASCADE'
)
lot_qt = fields.Float('Elements count')
quantity = fields.Numeric('Weight', digits=(16, 5))
operation_date = fields.Date('Date', required=True)
reversed_by = fields.Many2One(
'lot.split.merge', 'Reversed By'
)
class SplitLine(ModelView):
"Split Line"
__name__ = 'lot.split.wizard.line'
name = fields.Char('Lot name')
lot_qt = fields.Float('Quantity')
weight = fields.Numeric('Weight', digits=(16,5))
class SplitWizardStart(ModelView):
__name__ = 'lot.split.wizard.start'
mode = fields.Selection([
('equal', 'Equal parts'),
('manual', 'Manual'),
], 'Mode', required=True)
parts = fields.Integer(
'Parts',
states={'required': Eval('mode') == 'equal'}
)
create_remainder = fields.Boolean(
'Create remainder lot',
help='If checked, a remainder lot will be created when the split '
'does not exactly match the original weight.'
)
lines = fields.One2Many(
'lot.split.wizard.line',
None,
'Lines',
states={'invisible': Eval('mode') != 'manual'}
)
class SplitWizard(Wizard):
"Lot Split Wizard"
__name__ = 'lot.split.wizard'
start = StateView(
'lot.split.wizard.start',
'lot.split_wizard_start_view',
[
Button('Cancel', 'end', 'tryton-cancel'),
Button('Split', 'split', 'tryton-ok', default=True),
]
)
split = StateTransition()
def transition_split(self):
Lot = Pool().get('lot.lot')
lot = Lot(Transaction().context['active_id'])
if self.start.mode == 'equal':
part = lot.get_current_quantity() / self.start.parts
splits = [
{'quantity': part, 'name': f'{lot.lot_name}-{i+1}'}
for i in range(self.start.parts)
]
else:
splits = [
{'quantity': l.weight, 'lot_qt': l.lot_qt, 'name': l.name}
for l in self.start.lines
]
lot.split_by_weight(splits)
return 'end'
class SplitWizard(Wizard):
__name__ = 'lot.split.wizard'
start = StateView(
'lot.split.wizard.start',
'lot.split_wizard_start_view',
[
Button('Cancel', 'end', 'tryton-cancel'),
Button('Split', 'split', 'tryton-ok', default=True),
]
)
split = StateTransition()
def transition_split(self):
Lot = Pool().get('lot.lot')
lot = Lot(Transaction().context['active_id'])
total_weight = lot.get_current_quantity().quantize(
Decimal('0.00001'), rounding=ROUND_HALF_UP
)
splits = []
allocated = Decimal('0.0')
# -------------------------------------------------
# EQUAL MODE
# -------------------------------------------------
if self.start.mode == 'equal':
if not self.start.parts or self.start.parts <= 0:
raise UserError('Number of parts must be greater than zero.')
base = (total_weight / self.start.parts).quantize(
Decimal('0.00001'), rounding=ROUND_HALF_UP
)
for i in range(self.start.parts):
qty = base
if i == self.start.parts - 1:
qty = total_weight - allocated # absorbe le reste
splits.append({
'quantity': qty,
'lot_qt': lot.lot_qt / self.start.parts if lot.lot_qt else None,
'name': f'{lot.lot_name}-{i + 1}',
})
allocated += qty
# -------------------------------------------------
# MANUAL MODE
# -------------------------------------------------
else:
for line in self.start.lines:
if not line.weight or line.weight <= 0:
raise UserError('Each line must have a positive weight.')
splits.append({
'quantity': line.weight,
'lot_qt': line.lot_qt,
'name': line.name,
})
allocated += line.weight
diff = (total_weight - allocated).quantize(
Decimal('0.00001'), rounding=ROUND_HALF_UP
)
if diff != 0:
if self.start.create_remainder and diff > 0:
splits.append({
'quantity': diff,
'lot_qt': None,
'name': f'{lot.lot_name}-REMAINDER',
})
else:
raise UserError(
f'Split does not match the lot weight.\n'
f'Remaining difference: {diff}'
)
lot.split_by_weight(splits)
return 'end'
class MergeLotsStart(ModelView):
"Merge Lots Start"
__name__ = 'lot.merge.wizard.start'
confirm = fields.Boolean('Confirm merge')
class MergeLots(Wizard):
"Merge Lots"
__name__ = 'lot.merge.wizard'
start = StateView(
'lot.merge.wizard.start',
'lot.lot_merge_wizard_start_view',
[
Button('Cancel', 'end', 'tryton-cancel'),
Button('Merge', 'merge', 'tryton-ok', default=True),
]
)
merge = StateTransition()
def transition_merge(self):
Lot = Pool().get('lot.lot')
ids = Transaction().context['active_ids']
lots = Lot.browse(ids)
Lot.merge_lots(lots)
return 'end'

145
modules/lot/lot.xml Executable file
View File

@@ -0,0 +1,145 @@
<tryton>
<data>
<record model="ir.sequence.type" id="sequence_type_lot">
<field name="name">Lot</field>
</record>
<record model="ir.sequence" id="lot_sequence">
<field name="name">Lot sequence</field>
<field name="sequence_type" ref="sequence_type_lot"/>
</record>
<record model="ir.ui.view" id="lot_view_tree">
<field name="model">lot.lot</field>
<field name="type">tree</field>
<field name="priority" eval="10"/>
<field name="name">lot_tree</field>
</record>
<record model="ir.ui.view" id="lot_view_tree_sequence">
<field name="model">lot.lot</field>
<field name="type">tree</field>
<field name="priority" eval="20"/>
<field name="name">lot_tree_sequence</field>
</record>
<record model="ir.ui.view" id="lot_view_graph">
<field name="model">lot.lot</field>
<field name="type">graph</field>
<field name="priority" eval="20"/>
<field name="name">lot_graph</field>
</record>
<record model="ir.ui.view" id="lot_view_graph2">
<field name="model">lot.lot</field>
<field name="type">graph</field>
<field name="priority" eval="20"/>
<field name="name">lot_graph2</field>
</record>
<record model="ir.ui.view" id="lot_view_graph3">
<field name="model">lot.lot</field>
<field name="type">graph</field>
<field name="priority" eval="20"/>
<field name="name">lot_graph3</field>
</record>
<record model="ir.ui.view" id="lot_view_tree_sequence2">
<field name="model">lot.lot</field>
<field name="type">tree</field>
<field name="priority" eval="20"/>
<field name="name">lot_tree_sequence2</field>
</record>
<record model="ir.ui.view" id="lot_view_tree_sequence4">
<field name="model">lot.lot</field>
<field name="type">tree</field>
<field name="priority" eval="20"/>
<field name="name">lot_tree_sequence4</field>
</record>
<record model="ir.ui.view" id="lot_view_form">
<field name="model">lot.lot</field>
<field name="type">form</field>
<field name="name">lot_form</field>
</record>
<record model="ir.ui.view" id="lot_qt_view_tree">
<field name="model">lot.qt</field>
<field name="type">tree</field>
<field name="priority" eval="10"/>
<field name="name">lot_qt_tree</field>
</record>
<record model="ir.ui.view" id="lot_qt_view_tree_sequence">
<field name="model">lot.qt</field>
<field name="type">tree</field>
<field name="priority" eval="20"/>
<field name="name">lot_qt_tree_sequence</field>
</record>
<record model="ir.ui.view" id="lot_qt_type_view_tree">
<field name="model">lot.qt.type</field>
<field name="type">tree</field>
<field name="priority" eval="10"/>
<field name="name">lot_qt_type_tree</field>
</record>
<record model="ir.ui.view" id="lot_qt_type_view_tree_sequence">
<field name="model">lot.qt.type</field>
<field name="type">tree</field>
<field name="priority" eval="20"/>
<field name="name">lot_qt_type_tree_sequence</field>
</record>
<record model="ir.ui.view" id="lot_qt_hist_view_form">
<field name="model">lot.qt.hist</field>
<field name="type">form</field>
<field name="name">lot_qt_hist_form</field>
</record>
<record model="ir.ui.view" id="lot_qt_hist_view_tree">
<field name="model">lot.qt.hist</field>
<field name="type">tree</field>
<field name="priority" eval="10"/>
<field name="name">lot_qt_hist_tree</field>
</record>
<record model="ir.ui.view" id="lot_qt_hist_view_tree_sequence">
<field name="model">lot.qt.hist</field>
<field name="type">tree</field>
<field name="priority" eval="20"/>
<field name="name">lot_qt_hist_tree_sequence</field>
</record>
<record model="ir.ui.view" id="split_merge_tree_view">
<field name="model">lot.split.merge</field>
<field name="type">tree</field>
<field name="name">split_merge_tree</field>
</record>
<record model="ir.ui.view" id="split_line_tree_view">
<field name="model">lot.split.wizard.line</field>
<field name="type">tree</field>
<field name="name">split_line_tree</field>
</record>
<record model="ir.ui.view" id="split_wizard_start_view">
<field name="model">lot.split.wizard.start</field>
<field name="type">form</field>
<field name="name">split_start</field>
</record>
<record model="ir.action.wizard" id="action_split_wizard">
<field name="name">Split/Merge Lot</field>
<field name="wiz_name">lot.split.wizard</field>
<field name="model">lot.lot</field>
</record>
<record model="ir.action.keyword" id="act_split_keyword">
<field name="keyword">form_action</field>
<field name="model">lot.lot,-1</field>
<field name="action" ref="action_split_wizard"/>
</record>
<record model="ir.ui.view" id="lot_merge_wizard_start_view">
<field name="model">lot.merge.wizard.start</field>
<field name="type">form</field>
<field name="name">merge_start</field>
</record>
<record model="ir.action.wizard" id="action_merge_lots">
<field name="name">📦 Merge Lots</field>
<field name="wiz_name">lot.merge.wizard</field>
<field name="model">lot.report</field>
</record>
<record model="ir.action.keyword" id="act_merge_keyword">
<field name="keyword">form_action</field>
<field name="model">lot.report,-1</field>
<field name="action" ref="action_merge_lots"/>
</record>
</data>
</tryton>

123
modules/lot/setup.py Executable file
View File

@@ -0,0 +1,123 @@
#!/usr/bin/env python3
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
import io
import os
import re
from configparser import ConfigParser
from setuptools import find_packages, setup
def read(fname):
return io.open(
os.path.join(os.path.dirname(__file__), fname),
'r', encoding='utf-8').read()
def get_require_version(name):
require = '%s >= %s.%s, < %s.%s'
require %= (name, major_version, minor_version,
major_version, minor_version + 1)
return require
config = ConfigParser()
config.read_file(open(os.path.join(os.path.dirname(__file__), '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)
name = 'trytond_lot'
if minor_version % 2:
download_url = ''
else:
download_url = 'http://downloads.tryton.org/%s.%s/' % (
major_version, minor_version)
requires = []
for dep in info.get('depends', []):
if not re.match(r'(ir|res)(\W|$)', dep):
requires.append(get_require_version('trytond_%s' % dep))
requires.append(get_require_version('trytond'))
setup(name=name,
version=version,
description='Tryton module to add period to product',
author='Tryton',
author_email='foundation@tryton.org',
url='http://www.tryton.org/',
download_url=download_url,
project_urls={
"Bug Tracker": 'https://bugs.tryton.org/',
"Documentation": 'https://docs.tryton.org/',
"Forum": 'https://www.tryton.org/forum',
"Source Code": 'https://code.tryton.org/tryton',
},
keywords='tryton lot',
package_dir={'trytond.modules.lot': '.'},
packages=(
['trytond.modules.lot']
+ ['trytond.modules.lot.%s' % p
for p in find_packages()]
),
package_data={
'trytond.modules.lot': (info.get('xml', [])
+ ['tryton.cfg', 'view/*.xml', 'locale/*.po']),
},
classifiers=[
'Development Status :: 5 - Production/Stable',
'Environment :: Plugins',
'Framework :: Tryton',
'Intended Audience :: Developers',
'Intended Audience :: Financial and Insurance Industry',
'Intended Audience :: Legal Industry',
'Intended Audience :: Manufacturing',
'License :: OSI Approved :: '
'GNU General Public License v3 or later (GPLv3+)',
'Natural Language :: Bulgarian',
'Natural Language :: Catalan',
'Natural Language :: Chinese (Simplified)',
'Natural Language :: Czech',
'Natural Language :: Dutch',
'Natural Language :: English',
'Natural Language :: Finnish',
'Natural Language :: French',
'Natural Language :: German',
'Natural Language :: Hungarian',
'Natural Language :: Indonesian',
'Natural Language :: Italian',
'Natural Language :: Persian',
'Natural Language :: Polish',
'Natural Language :: Portuguese (Brazilian)',
'Natural Language :: Romanian',
'Natural Language :: Russian',
'Natural Language :: Slovenian',
'Natural Language :: Spanish',
'Natural Language :: Turkish',
'Natural Language :: Ukrainian',
'Operating System :: OS Independent',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12',
'Programming Language :: Python :: Implementation :: CPython',
'Topic :: Office/Business',
],
license='GPL-3',
python_requires='>=3.8',
install_requires=requires,
zip_safe=False,
entry_points="""
[trytond.modules]
lot = trytond.modules.lot
""",
)

10
modules/lot/tryton.cfg Executable file
View File

@@ -0,0 +1,10 @@
[tryton]
version=7.2.7
depends:
ir
res
product
stock
price
xml:
lot.xml

View File

@@ -0,0 +1,53 @@
Metadata-Version: 2.1
Name: trytond-lot
Version: 7.2.7
Summary: Tryton module to add period to product
Home-page: http://www.tryton.org/
Download-URL: http://downloads.tryton.org/7.2/
Author: Tryton
Author-email: foundation@tryton.org
License: GPL-3
Project-URL: Bug Tracker, https://bugs.tryton.org/
Project-URL: Documentation, https://docs.tryton.org/
Project-URL: Forum, https://www.tryton.org/forum
Project-URL: Source Code, https://code.tryton.org/tryton
Keywords: tryton lot
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Plugins
Classifier: Framework :: Tryton
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: Intended Audience :: Legal Industry
Classifier: Intended Audience :: Manufacturing
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
Classifier: Natural Language :: Bulgarian
Classifier: Natural Language :: Catalan
Classifier: Natural Language :: Chinese (Simplified)
Classifier: Natural Language :: Czech
Classifier: Natural Language :: Dutch
Classifier: Natural Language :: English
Classifier: Natural Language :: Finnish
Classifier: Natural Language :: French
Classifier: Natural Language :: German
Classifier: Natural Language :: Hungarian
Classifier: Natural Language :: Indonesian
Classifier: Natural Language :: Italian
Classifier: Natural Language :: Persian
Classifier: Natural Language :: Polish
Classifier: Natural Language :: Portuguese (Brazilian)
Classifier: Natural Language :: Romanian
Classifier: Natural Language :: Russian
Classifier: Natural Language :: Slovenian
Classifier: Natural Language :: Spanish
Classifier: Natural Language :: Turkish
Classifier: Natural Language :: Ukrainian
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Topic :: Office/Business
Requires-Python: >=3.8

View File

@@ -0,0 +1,21 @@
setup.py
./__init__.py
./lot.py
./lot.xml
./tryton.cfg
./view/lot_form.xml
./view/lot_qt_tree.xml
./view/lot_qt_tree_sequence.xml
./view/lot_qt_type_tree.xml
./view/lot_qt_type_tree_sequence.xml
./view/lot_tree.xml
./view/lot_tree_sequence.xml
./view/lot_tree_sequence2.xml
./view/lot_tree_sequence3.xml
trytond_lot.egg-info/PKG-INFO
trytond_lot.egg-info/SOURCES.txt
trytond_lot.egg-info/dependency_links.txt
trytond_lot.egg-info/entry_points.txt
trytond_lot.egg-info/not-zip-safe
trytond_lot.egg-info/requires.txt
trytond_lot.egg-info/top_level.txt

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,2 @@
[trytond.modules]
lot = trytond.modules.lot

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,3 @@
trytond<7.3,>=7.2
trytond_purchase<7.3,>=7.2
trytond_sale<7.3,>=7.2

View File

@@ -0,0 +1 @@
trytond

49
modules/lot/view/lot_form.xml Executable file
View File

@@ -0,0 +1,49 @@
<?xml version="1.0"?>
<form col="4">
<label name="lot_name"/>
<field name="lot_name"/>
<label name="lot_parent"/>
<field name="lot_parent"/>
<label name="lot_qt"/>
<field name="lot_qt"/>
<label name="lot_unit"/>
<field name="lot_unit"/>
<newline/>
<label name="lot_product"/>
<field name="lot_product"/>
<label name="lot_type"/>
<field name="lot_type"/>
<label name="lot_status"/>
<field name="lot_status"/>
<label name="lot_state"/>
<field name="lot_state"/>
<newline/>
<label name="lot_quantity"/>
<field name="lot_quantity"/>
<label name="lot_gross_quantity"/>
<field name="lot_gross_quantity"/>
<newline/>
<label name="lot_unit_line"/>
<field name="lot_unit_line"/>
<label name="lot_premium"/>
<field name="lot_premium"/>
<newline/>
<label name="warrant_nb"/>
<field name="warrant_nb"/>
<label name="lot_role"/>
<field name="lot_role"/>
<newline/>
<notebook colspan="4">
<page string="General" id="general">
<field name="lot_childs" colspan="4" mode="tree,form" view_ids="lot.lot_view_tree_sequence2,lot.lot_view_form"/>
<field name="lot_hist" colspan="4"/>
</page>
<page string="Accounting" id="accounting">
<field name="pivot" widget="html_viewer" height="600" colspan="4"/>
</page>
<page string="Split/Merge" id="split">
<field name="split_operations" colspan="4" mode="tree" view_ids="lot.split_merge_tree_view"/>
<field name="split_graph" widget="html_viewer" colspan="4"/>
</page>
</notebook>
</form>

12
modules/lot/view/lot_graph.xml Executable file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<graph type="pie">
<x>
<field name="lot_product"/>
</x>
<y>
<field name="lot_price" string="USD Pprice"/>
<field name="lot_quantity" string="Quantity sold Mt"/>
</y>
</graph>

12
modules/lot/view/lot_graph2.xml Executable file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<graph type="vbar">
<x>
<field name="lot_product"/>
</x>
<y>
<field name="lot_price" string="USD Price"/>
<field name="lot_quantity" string="Quantity sold Mt"/>
</y>
</graph>

12
modules/lot/view/lot_graph3.xml Executable file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<graph type="line">
<x>
<field name="lot_product"/>
</x>
<y>
<field name="lot_price" string="USD Price"/>
<field name="lot_quantity" string="Quantity sold Mt"/>
</y>
</graph>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0"?>
<form col="4">
<label name="lot"/>
<field name="lot"/>
<label name="quantity_type"/>
<field name="quantity_type"/>
<label name="quantity"/>
<field name="quantity"/>
<label name="gross_quantity"/>
<field name="gross_quantity"/>
</form>

View File

@@ -0,0 +1,6 @@
<tree editable="1">
<field name="lot"/>
<field name="quantity_type"/>
<field name="quantity"/>
<field name="gross_quantity"/>
</tree>

View File

@@ -0,0 +1,6 @@
<tree editable="1">
<field name="lot"/>
<field name="quantity_type"/>
<field name="quantity"/>
<field name="gross_quantity"/>
</tree>

View File

@@ -0,0 +1,4 @@
<tree>
<field name="quantity_type"/>
<field name="quantity"/>
</tree>

View File

@@ -0,0 +1,8 @@
<tree>
<field name="lot_p"/>
<field name="lot_quantity"/>
<field name="lot_unit"/>
<field name="lot_av"/>
<field name="lot_status"/>
<field name="lot_s"/>
</tree>

View File

@@ -0,0 +1,4 @@
<tree>
<field name="name"/>
<field name="sequence"/>
</tree>

View File

@@ -0,0 +1,4 @@
<tree>
<field name="name"/>
<field name="sequence"/>
</tree>

12
modules/lot/view/lot_tree.xml Executable file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0"?>
<tree>
<field name="id"/>
<field name="line"/>
<field name="sale_line"/>
<field name="lot_type"/>
<field name="lot_status"/>
<field name="lot_quantity"/>
<field name="lot_unit"/>
<field name="lot_gross_quantity"/>
<field name="lot_parent"/>
</tree>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0"?>
<tree sequence="sequence" editable="0">
<field name="lot_name" />
<field name="lot_type" />
<field name="lot_qt"/>
<field name="lot_unit"/>
<field name="lot_product"/>
<field name="lot_shipment_origin"/>
<field name="lot_quantity"/>
<field name="lot_gross_quantity"/>
<field name="lot_unit_line"/>
<field name="lot_premium" width="80"/>
<field name="lot_premium_sup" width="80"/>
<field name="lot_price_ct_symbol_premium"/>
<field name="lot_price"/>
<field name="lot_price_ct_symbol"/>
<field name="lot_type" width="80" optional="1"/>
<field name="lot_status" width="80" optional="1"/>
</tree>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0"?>
<tree sequence="sequence" editable="1">
<field name="id" width="80"/>
<field name="lot_qt" width="80"/>
<field name="lot_unit" width="80"/>
<field name="lot_quantity" symbol="r_lot_unit_line" width="80"/>
<field name="lot_gross_quantity" symbol="r_lot_unit_line" width="80"/>
</tree>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0"?>
<tree sequence="sequence" editable="1">
<field name="lot_himself" width="80"/>
<field name="lot_qt" width="80"/>
<field name="lot_product" width="80"/>
<field name="lot_type" width="80" optional="1"/>
<field name="lot_status" width="80" optional="1"/>
<field name="lot_quantity" width="80"/>
<field name="lot_unit" width="80"/>
<field name="lot_gross_quantity" width="80"/>
<field name="lot_premium_sale" width="80"/>
<field name="lot_price_sale" width="80"/>
</tree>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0"?>
<tree sequence="sequence" editable="0">
<field name="lot_name" width="80"/>
<field name="lot_qt" width="80"/>
<field name="lot_unit" width="80"/>
<field name="lot_quantity_sale" symbol="r_lot_unit_line" width="80"/>
<field name="lot_gross_quantity_sale" symbol="r_lot_unit_line" width="80"/>
</tree>

View File

@@ -0,0 +1,4 @@
<form>
<label name="confirm"/>
<field name="confirm"/>
</form>

View File

@@ -0,0 +1,5 @@
<tree>
<field name="name"/>
<field name="lot_qt"/>
<field name="weight"/>
</tree>

View File

@@ -0,0 +1,7 @@
<tree>
<field name="operation"/>
<field name="target_lot"/>
<field name="lot_qt"/>
<field name="quantity"/>
<field name="operation_date"/>
</tree>

View File

@@ -0,0 +1,9 @@
<form col="2">
<label name="mode"/>
<field name="mode"/>
<label name="parts"/>
<field name="parts"/>
<label name="create_remainder"/>
<field name="create_remainder"/>
<field name="lines" colspan="2"/>
</form>