Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[14.0][ADD] connector_oxigesti: export manufacturing and unbuild orders #301

Open
wants to merge 9 commits into
base: 14.0
Choose a base branch
from
1 change: 1 addition & 0 deletions connector_oxigesti/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Contributors

* Eric Antones <[email protected]>
* Kilian Niubo <[email protected]>
* Frank Cespedes <[email protected]>



Expand Down
2 changes: 2 additions & 0 deletions connector_oxigesti/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"sale_line_partner_description",
"sale_specific_order_date",
"oxigen_stock_alternate_lot",
"oxigen_mrp",
],
"external_dependencies": {
"python": [
Expand All @@ -34,6 +35,7 @@
"views/product_pricelist_item_view.xml",
"views/stock_production_lot_view.xml",
"views/sale_order_view.xml",
"views/mrp_production_view.xml",
"views/connector_oxigesti_menu.xml",
"security/connector_oxigesti.xml",
"security/ir.model.access.csv",
Expand Down
39 changes: 39 additions & 0 deletions connector_oxigesti/components/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,25 @@ class GenericAdapter(AbstractComponent):
_name = "oxigesti.adapter"
_inherit = "oxigesti.crud.adapter"

@property
def PYTHON_TYPE_MAP(self):
return {
int: ["int"],
float: ["float", "numeric"],
str: ["nvarchar"],
bool: ["bit"],
}

@property
def MSSQL_TYPE_MAP(self):
mssql_type_map = {}
for k, v in self.PYTHON_TYPE_MAP.items():
for t in v:
if t in mssql_type_map:
raise ValidationError(_("Duplicated type %s in MSSQL_TYPE_MAP") % t)
mssql_type_map[t] = k
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check if "t" is already in mssql_type_map, ValidationError if is

return mssql_type_map

# private methods
def _convert_dict(self, data, to_backend=True):
if not isinstance(data, dict):
Expand Down Expand Up @@ -228,6 +247,26 @@ def _exec_query(self, filters=None, fields=None, as_dict=True):

return res

def get_headers(self):
# prepare the sql and execute
if not hasattr(self, "_sql_field_type"):
raise ValidationError(
_("The adapter %s doesn't have a _sql_field_type defined") % self._name
)
conn = self.conn()
cr = conn.cursor()
cr.execute(self._sql_field_type, dict(schema=self.schema))
res = {}
for field, ttype in cr.fetchall():
if ttype not in self.MSSQL_TYPE_MAP:
raise ValidationError(
_("Unexpected type %s for field %s") % (ttype, field)
Comment on lines +262 to +263
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put this line as a first if

)
res[field] = self.MSSQL_TYPE_MAP[ttype]
cr.close()
conn.close()
return res

def _check_uniq(self, data):
uniq = set()
for rec in data:
Expand Down
19 changes: 18 additions & 1 deletion connector_oxigesti/data/ir_cron.xml
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,24 @@
<field name="state">code</field>
<field name="code">model._scheduler_import_stock_production_lot()</field>
</record>

<record
id="ir_cron_oxigesti_export_mrp_production"
model="ir.cron"
forcecreate="True"
>
<field name="name">Oxigesti OS - Export Productions</field>
<field name="model_id" ref="model_oxigesti_backend" />
<field name="active" eval="False" />
<!-- Mandatory to define a explicit user on record from the frontend,
DON'T USE admin (base.user_root) -->
<!--field name="user_id" ref="<not base.user_root>"/-->
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field name="doall" eval="False" />
<field name="state">code</field>
<field name="code">model._scheduler_export_mrp_production()</field>
</record>
<record id="ir_cron_oxigesti_import_services" model="ir.cron" forcecreate="True">
<field name="name">Oxigesti OS - Import services</field>
<field name="model_id" ref="model_oxigesti_backend" />
Expand Down
20 changes: 19 additions & 1 deletion connector_oxigesti/data/queue_job_function_data.xml
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,25 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)-->
<field name="channel_id" ref="connector_oxigesti.channel_oxigesti" />
<field name="retry_pattern" eval="{1: 10, 5: 30, 10: 60, 15: 300}" />
</record>

<!-- Productions -->
<record
id="oxigesti_mrp_production_export_batch_job_function"
model="queue.job.function"
>
<field name="model_id" ref="connector_oxigesti.model_oxigesti_mrp_production" />
<field name="method">export_batch</field>
<field name="channel_id" ref="connector_oxigesti.channel_oxigesti_batch" />
<field name="retry_pattern" eval="{1: 10, 5: 30, 10: 60, 15: 300}" />
</record>
<record
id="oxigesti_mrp_production_export_record_job_function"
model="queue.job.function"
>
<field name="model_id" ref="connector_oxigesti.model_oxigesti_mrp_production" />
<field name="method">export_record</field>
<field name="channel_id" ref="connector_oxigesti.channel_oxigesti" />
<field name="retry_pattern" eval="{1: 10, 5: 30, 10: 60, 15: 300}" />
</record>
<!-- EXPORTS-->
<record
id="oxigesti_sale_order_export_batch_job_function"
Expand Down
1 change: 1 addition & 0 deletions connector_oxigesti/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@
from . import sale_order
from . import account_invoice
from . import stock_picking
from . import mrp_production
7 changes: 7 additions & 0 deletions connector_oxigesti/models/mrp_production/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from . import exporter
from . import adapter
from . import export_mapper
from . import binder
from . import binding

# from . import listener
32 changes: 32 additions & 0 deletions connector_oxigesti/models/mrp_production/adapter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Copyright NuoBiT Solutions - Frank Cespedes <[email protected]>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)

from odoo.addons.component.core import Component


class MrpProductionAdapter(Component):
_name = "oxigesti.mrp.production.adapter"
_inherit = "oxigesti.adapter"

_apply_on = "oxigesti.mrp.production"

_sql = """select a.Id, a.CodigoOrdenProduccion, a.FechaProduccion,
a.CodigoOrdenDeconstruccion, a.CodigoBotellaVacia,
a.LoteBotellaVacia, a.CodigoCilindro, a.LoteCilindro,
a.CodigoValvula, a.LoteValvula
from %(schema)s.Odoo_Orden_Produccion a
"""

_sql_update = """update s
set %(qset)s
from %(schema)s.Odoo_Orden_Produccion s
where s.CodigoOrdenProduccion = %%(CodigoOrdenProduccion)s
"""

_sql_insert = """insert into %(schema)s.Odoo_Orden_Produccion
(%(fields)s)
output %(retvalues)s
values (%(phvalues)s)
"""

_id = ("Id",)
21 changes: 21 additions & 0 deletions connector_oxigesti/models/mrp_production/binder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Copyright NuoBiT Solutions - Frank Cespedes <[email protected]>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)

from odoo.addons.component.core import Component


class MrpProductionBinder(Component):
_name = "oxigesti.mrp.production.binder"
_inherit = "oxigesti.binder"

_apply_on = "oxigesti.mrp.production"

# def _get_external_id(self, binding):
# if not self._is_binding(binding):
# raise Exception("The source object %s must be a binding" % binding._name)
#
# external_id = None
# if binding.odoo_id.product_id.default_code:
# external_id = [binding.odoo_id.product_id.default_code]
#
# return external_id
88 changes: 88 additions & 0 deletions connector_oxigesti/models/mrp_production/binding.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Copyright NuoBiT Solutions - Frank Cespedes <[email protected]>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError


class MrpProduction(models.Model):
_inherit = "mrp.production"

oxigesti_bind_ids = fields.One2many(
comodel_name="oxigesti.mrp.production",
inverse_name="odoo_id",
string="Oxigesti Bindings",
)

def _get_valid_components(self):
fields = ["cylinder", "valve"]
moves = self.env["stock.move"]
for mrp_type in fields:
move_raw = self.move_raw_ids.filtered(
lambda x: x.product_id.mrp_type == mrp_type
and x.quantity_done > 0
and x.move_line_ids
)
if len(move_raw.product_id) == 0:
raise ValidationError(
_("Production of empty cylinder type without %s product: %s")
% (mrp_type, self.name)
)
if len(move_raw) > 1 or sum(move_raw.mapped("quantity_done")) > 1:
raise ValidationError(
_(
"The empty cylinder (%s) has been created with"
" more than one %s"
)
% (self.name, mrp_type)
)
if len(move_raw.move_line_ids) > 1:
raise ValidationError(
_(
"You have a component with more than one serial"
" number to generate: %s"
)
% self.name
)
if not move_raw.product_id.default_code:
raise ValidationError(
_("Internal Reference not set in product: %s")
% move_raw.product_id.name
)
moves |= move_raw
return moves


class MrpProductionBinding(models.Model):
_name = "oxigesti.mrp.production"
_inherit = "oxigesti.binding"
_inherits = {"mrp.production": "odoo_id"}
_description = "Product Mrp Production"

odoo_id = fields.Many2one(
comodel_name="mrp.production",
string="Production",
required=True,
ondelete="cascade",
)

@api.model
def export_data(self, backend, since_date):
domain = [
("company_id", "=", backend.company_id.id),
("product_id.mrp_type", "=", "empty_cylinder"),
("state", "=", "done"),
]
if since_date:
domain += [("write_date", ">", since_date)]
self.with_delay().export_batch(backend, domain=domain)

def resync(self):
for record in self:
with record.backend_id.work_on(record._name) as work:
binder = work.component(usage="binder")
relation = binder.unwrap_binding(self)
func = record.export_record
if record.env.context.get("connector_delay"):
func = record.export_record.delay
func(record.backend_id, relation)
return True
70 changes: 70 additions & 0 deletions connector_oxigesti/models/mrp_production/export_mapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Copyright NuoBiT Solutions - Frank Cespedes <[email protected]>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
from odoo import _
from odoo.exceptions import ValidationError

from odoo.addons.component.core import Component
from odoo.addons.connector.components.mapper import follow_m2o_relations, mapping, none


class MrpProductionExportMapper(Component):
_name = "oxigesti.mrp.production.export.mapper"
_inherit = "oxigesti.export.mapper"

_apply_on = "oxigesti.mrp.production"

direct = [
("name", "CodigoOrdenProduccion"),
(none("date_planned_start"), "FechaProduccion"),
(none(follow_m2o_relations("lot_producing_id.name")), "LoteBotellaVacia"),
]

@mapping
def CodigoBotellaVacia(self, record):
if not record.product_id.default_code:
raise ValidationError(
_("Internal Reference not set in product: %s") % record.product_id.name
)
return {"CodigoBotellaVacia": record.product_id.default_code}

@mapping
def CodigoOrdenDeconstruccion(self, record):
unbuild = self.env["mrp.unbuild"].search(
[("mo_id", "=", record.odoo_id.id), ("state", "=", "done")]
)
if len(unbuild) > 1:
raise ValidationError(
_("The production %s has more than one unbuild. %s")
% (record.name, unbuild.mapped("name"))
)
return {"CodigoOrdenDeconstruccion": unbuild.name or None}

@mapping
def Componentes(self, record):
binder = self.binder_for("oxigesti.mrp.production")
mrp_production = binder.unwrap_binding(record)
move_raws = mrp_production._get_valid_components()
mrp_type_map = {
"cylinder": ("CodigoCilindro", "LoteCilindro"),
"valve": ("CodigoValvula", "LoteValvula"),
}
res = {}
for move_raw in move_raws:
mrp_type = move_raw.product_id.mrp_type
if mrp_type not in mrp_type_map:
raise ValidationError(_("Invalid product type: %s") % mrp_type)
default_code = move_raw.product_id.default_code
if not default_code:
raise ValidationError(
_("Internal Reference not set in product: %s")
% move_raw.product_id.name
)
lot = move_raw.move_line_ids.lot_id
if not lot:
raise ValidationError(
_("Serial Number not set in product: %s") % move_raw.product_id.name
)
code_key, lot_key = mrp_type_map[mrp_type]
res[code_key] = default_code
res[lot_key] = lot.name
return res
Loading
Loading