Skip to content

Commit

Permalink
Merge pull request #454 from frePPLe/17.0
Browse files Browse the repository at this point in the history
Syncing from upstream frePPLe/odoo (17.0)
  • Loading branch information
bt-admin authored Jul 5, 2024
2 parents 9886a17 + f912be9 commit a132bdf
Show file tree
Hide file tree
Showing 2 changed files with 186 additions and 6 deletions.
2 changes: 2 additions & 0 deletions frepple/controllers/frepplexml.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ def xml(self, **kwargs):
company = i
if not company:
return Response("Invalid company name argument", 401)
apps = kwargs.get("apps", req.httprequest.form.get("apps", None)) or ""

if req.httprequest.method == "GET":
# Generate data
Expand All @@ -209,6 +210,7 @@ def xml(self, **kwargs):
version=version,
delta=float(kwargs.get("delta", 999)),
language=language,
apps=apps,
)
# last empty double quote is to let python understand frepple is a folder.
xml_folder = os.path.join(str(Path.home()), "logs", "frepple", "")
Expand Down
190 changes: 184 additions & 6 deletions frepple/controllers/outbound.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ def __init__(
version="0.0.0.unknown",
delta=999,
language="en_US",
apps="",
):
self.database = database
self.company = company
Expand Down Expand Up @@ -199,6 +200,19 @@ def __init__(
)
> 0
)
self.has_expiry = (
len(
self.generator.getData(
"ir.module.module",
search=[
("state", "=", "installed"),
("name", "=", "mrp_product_expiry"),
],
fields=["id"],
)
)
> 0
) and "freppledb.shelflife" in apps

# The mode argument defines different types of runs:
# - Mode 1:
Expand Down Expand Up @@ -291,8 +305,13 @@ def run(self):
yield from self.export_manufacturingorders()
logger.debug("Exporting reordering rules.")
yield from self.export_orderpoints()
logger.debug("Exporting quantities on-hand.")
yield from self.export_onhand()

if self.has_expiry:
logger.debug("Exporting stock orders.")
yield from self.export_stockorders()
else:
logger.debug("Exporting quantities on-hand.")
yield from self.export_onhand()

# Footer
yield "</plan>\n"
Expand Down Expand Up @@ -1019,7 +1038,14 @@ def export_items(self):
"categ_id",
"product_variant_ids",
"route_ids",
],
]
+ (
[
"expiration_time",
]
if self.has_expiry
else []
),
):
self.product_templates[i["id"]] = i

Expand Down Expand Up @@ -1110,7 +1136,7 @@ def export_items(self):
self.product_template_product[i["product_tmpl_id"][0]] = prod_obj

# For make-to-order items the next line needs to XML snippet ' type="item_mto"'.
yield '<item name=%s %s uom=%s volume="%f" weight="%f" cost="%f" category=%s subcategory="%s,%s"%s>\n' % (
yield '<item name=%s %s uom=%s volume="%f" weight="%f" cost="%f" category=%s subcategory="%s,%s"%s%s>\n' % (
quoteattr(name),
(
("description=%s" % (quoteattr(description),))
Expand All @@ -1129,6 +1155,16 @@ def export_items(self):
tmpl["uom_id"][0],
i["id"],
' type="item_mto"' if self.route_mto in tmpl["route_ids"] else "",
(
(
' shelflife="%s"'
% self.convert_float_time(tmpl["expiration_time"])
)
if self.has_expiry
and tmpl["expiration_time"]
and tmpl["expiration_time"] > 0
else ""
),
)
# Export suppliers for the item, if the item is allowed to be purchased
if tmpl["purchase_ok"]:
Expand Down Expand Up @@ -2155,6 +2191,28 @@ def export_purchaseorders(self):
start = self.formatDateTime(start if start < end else end)
end = self.formatDateTime(end)
qty = mv.product_qty - mv.quantity
supplier = self.map_customers.get(j.partner_id.id)
if not supplier:
# supplier is archived :-(
for sup in self.generator.getData(
"res.partner",
search=[
("id", "=", j.partner_id.id),
"|",
("active", "=", True),
("active", "=", False),
],
fields=["name", "active"],
):
supplier = "%s %s%s" % (
sup["name"],
"(archived) " if not sup["active"] else "",
sup["id"],
)
self.map_customers[sup["id"]] = supplier
break
if not supplier:
continue
if qty >= 0:
yield '<operationplan reference=%s %sordertype="PO" start="%s" end="%s" quantity="%f" status="confirmed">' "<item name=%s/><location name=%s/><supplier name=%s/></operationplan>\n" % (
quoteattr(po_line_reference),
Expand All @@ -2164,7 +2222,7 @@ def export_purchaseorders(self):
qty,
quoteattr(item["name"]),
quoteattr(location),
quoteattr(self.map_customers.get(j.partner_id.id)),
quoteattr(supplier),
)
else:
# METHOD 2: Create purchasing operations from purchase order lines
Expand All @@ -2189,6 +2247,28 @@ def export_purchaseorders(self):
i.product_uom.id,
self.product_product[i.product_id.id]["template"],
)
supplier = self.map_customers.get(j.partner_id.id)
if not supplier:
# supplier is archived :-(
for sup in self.generator.getData(
"res.partner",
search=[
("id", "=", j.partner_id.id),
"|",
("active", "=", True),
("active", "=", False),
],
fields=["name", "active"],
):
supplier = "%s %s%s" % (
sup["name"],
"(archived) " if not sup["active"] else "",
sup["id"],
)
self.map_customers[sup["id"]] = supplier
break
if not supplier:
continue

# MTO links
if (
Expand All @@ -2212,7 +2292,7 @@ def export_purchaseorders(self):
qty,
quoteattr(item["name"]),
quoteattr(location),
quoteattr(self.map_customers.get(j.partner_id.id)),
quoteattr(supplier),
)
yield "</operationplans>\n"

Expand Down Expand Up @@ -2705,6 +2785,104 @@ def export_orderpoints(self):
if not first:
yield "</calendars>\n"

# export_stockorders will be called instead of export_onhand
# when expiration dates is enabled in Odoo

def export_stockorders(self):
"""
Extracting all on hand inventories to frePPLe.
We're bypassing the ORM for performance reasons.
Mapping:
stock.report.prodlots.product_id.name @ stock.report.prodlots.location_id.name -> buffer.name
stock.report.prodlots.product_id.name -> buffer.item
stock.report.prodlots.location_id.name -> buffer.location
sum(stock.report.prodlots.qty) -> buffer.onhand
"""
yield "<!-- inventory -->\n"
yield "<operationplans>\n"
if isinstance(self.generator, Odoo_generator):
# SQL query gives much better performance
self.generator.env.cr.execute(
"""
SELECT stock_quant.product_id,
stock_quant.location_id,
sum(stock_quant.quantity) as quantity,
sum(stock_quant.reserved_quantity) as reserved_quantity,
stock_production_lot.name as lot_name,
stock_production_lot.expiration_date
FROM stock_quant
left outer join stock_production_lot on stock_quant.lot_id = stock_production_lot.id
and stock_production_lot.product_id = stock_quant.product_id
WHERE quantity > 0
GROUP BY stock_quant.product_id,
stock_quant.location_id,
stock_production_lot.name,
stock_production_lot.expiration_date
ORDER BY location_id ASC
"""
)
data = self.generator.env.cr.fetchall()
else:
data = [
(i["product_id"][0], i["location_id"][0], i["quantity"])
for i in self.generator.getData(
"stock.quant",
search=[("quantity", ">", 0)],
fields=[
"product_id",
"location_id",
"quantity",
"reserved_quantity",
],
)
if i["product_id"] and i["location_id"]
]
inventory = {}
expirationdate = {}
for i in data:
item = self.product_product.get(i[0], None)
location = self.map_locations.get(i[1], None)
lotname = i[4]
if item and location:
inventory[(item["name"], location, lotname)] = max(
0,
inventory.get((item["name"], location, lotname), 0)
+ i[2]
- (i[3] if self.respect_reservations else 0),
)
if i[5]:
expirationdate[(item["name"], location, lotname)] = i[5]
for key, val in inventory.items():
yield (
"""
<operationplan ordertype="STCK" end="%s" reference=%s %s quantity="%s">
<item name=%s/>
<location name=%s/>
</operationplan>
"""
% (
self.formatDateTime(datetime.now()),
quoteattr(
"STCK %s @ %s%s"
% (key[0], key[1], (" @ %s" % (key[2],)) if key[2] else "")
),
(
('expiry="%s"' % self.formatDateTime(expirationdate[key]))
if key in expirationdate
else ""
),
val or 0,
quoteattr(key[0]),
quoteattr(key[1]),
)
)
yield "</operationplans>\n"

# export_stockorders will be called instead of export_onhand
# when expiration dates is enabled in Odoo

def export_onhand(self):
"""
Extracting all on hand inventories to frePPLe.
Expand Down

0 comments on commit a132bdf

Please sign in to comment.