mirror of
https://github.com/frappe/erpnext.git
synced 2026-07-07 23:42:28 +00:00
Compare commits
8 Commits
copilot/de
...
mihir-kand
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c003d911f1 | ||
|
|
2d3190effb | ||
|
|
a68769565b | ||
|
|
19234cafbe | ||
|
|
09623d4c0c | ||
|
|
032a282f84 | ||
|
|
ca093177e0 | ||
|
|
ea3cf57042 |
70
.github/workflows/propogate-auto-merge.yml
vendored
Normal file
70
.github/workflows/propogate-auto-merge.yml
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
name: Propagate auto-merge label to backports
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
propagate-label:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
issues: write
|
||||
|
||||
steps:
|
||||
- name: Detect backport and original PR (metadata)
|
||||
id: extract
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const pr = context.payload.pull_request;
|
||||
const base = pr.base.ref;
|
||||
if (!/^version-(15|16)-hotfix$/.test(base)) {
|
||||
core.setOutput('is_backport', 'false');
|
||||
return;
|
||||
}
|
||||
|
||||
const isMergify = pr.user && pr.user.login === 'mergify[bot]';
|
||||
const headRef = pr.head.ref || '';
|
||||
const match = headRef.match(/(?:^|\/)pr-(\d+)(?:-|$)/i);
|
||||
|
||||
if (isMergify && match) {
|
||||
const original = Number(match[1]);
|
||||
if (!Number.isNaN(original)) {
|
||||
core.setOutput('original_pr', String(original));
|
||||
core.setOutput('is_backport', 'true');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
core.setOutput('is_backport', 'false');
|
||||
|
||||
- name: Check original PR labels
|
||||
if: steps.extract.outputs.is_backport == 'true'
|
||||
id: check
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const prNumber = Number("${{ steps.extract.outputs.original_pr }}");
|
||||
|
||||
const { data } = await github.rest.issues.get({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber
|
||||
});
|
||||
|
||||
const labels = data.labels.map(l => l.name);
|
||||
core.setOutput("has_auto_merge", labels.includes("auto-merge backports"));
|
||||
|
||||
- name: Add auto-merge label to backport PR
|
||||
if: steps.check.outputs.has_auto_merge == 'true'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.payload.pull_request.number,
|
||||
labels: ["auto-merge"]
|
||||
});
|
||||
15
.mergify.yml
15
.mergify.yml
@@ -92,3 +92,18 @@ pull_request_rules:
|
||||
{{ title }} (#{{ number }})
|
||||
|
||||
{{ body }}
|
||||
- name: Auto merge backports (safe)
|
||||
conditions:
|
||||
- label=auto-merge
|
||||
- base~=^version-(15|16)-hotfix$
|
||||
- -conflict
|
||||
- status-success=Python Unit Tests (1)
|
||||
- status-success=Python Unit Tests (2)
|
||||
- status-success=Python Unit Tests (3)
|
||||
- status-success=Python Unit Tests (4)
|
||||
- status-success=linters
|
||||
- status-success=semgrep
|
||||
- status-success=Patch Test
|
||||
actions:
|
||||
merge:
|
||||
method: squash
|
||||
10
README.md
10
README.md
@@ -10,7 +10,7 @@
|
||||
|
||||
[](https://frappe.school)<br><br>
|
||||
[](https://github.com/frappe/erpnext/actions/workflows/server-tests-mariadb.yml)
|
||||
[](https://hub.docker.com/r/frappe/erpnext-worker)
|
||||
[](https://hub.docker.com/r/frappe/erpnext)
|
||||
|
||||
</div>
|
||||
|
||||
@@ -88,14 +88,6 @@ See [Frappe Docker Documentation](https://github.com/frappe/frappe_docker) for f
|
||||
|
||||
> For Docker basics and best practices refer to Docker's [documentation](https://docs.docker.com)
|
||||
|
||||
#### Demo setup
|
||||
|
||||
The fastest way to try ERPNext is to play in a pre-configured sandbox, in your browser, click the button below:
|
||||
|
||||
<a href="https://labs.play-with-docker.com/?stack=https://raw.githubusercontent.com/frappe/frappe_docker/main/pwd.yml">
|
||||
<img src="https://raw.githubusercontent.com/play-with-docker/stacks/master/assets/images/button.png" alt="Try in PWD"/>
|
||||
</a>
|
||||
|
||||
### Try on your environment
|
||||
|
||||
> **⚠️ Disposable demo only**
|
||||
|
||||
@@ -34,6 +34,13 @@
|
||||
"account_number": "0430",
|
||||
"account_type": "Fixed Asset"
|
||||
},
|
||||
"Anlagen im Bau": {
|
||||
"is_group": 1,
|
||||
"Andere Anlagen, Betriebs- und Geschäftsausstattung im Bau": {
|
||||
"account_number": "0498",
|
||||
"account_type": "Capital Work in Progress"
|
||||
}
|
||||
},
|
||||
"Accumulated Depreciation": {
|
||||
"account_type": "Accumulated Depreciation"
|
||||
}
|
||||
@@ -317,13 +324,21 @@
|
||||
"account_number": "3800",
|
||||
"account_type": "Expenses Included In Asset Valuation"
|
||||
},
|
||||
"Bestandsveränderungen Roh-, Hilfs- und Betriebsstoffe sowie bezogene Waren": {
|
||||
"account_number": "3960",
|
||||
"account_type": "Stock Adjustment"
|
||||
},
|
||||
"Herstellungskosten": {
|
||||
"account_number": "4996",
|
||||
"account_type": "Cost of Goods Sold"
|
||||
},
|
||||
"Anlagenabgänge Sachanlagen (Restbuchwert bei Buchverlust)": {
|
||||
"account_number": "2310",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Verluste aus dem Abgang von Gegenständen des Anlagevermögens": {
|
||||
"account_number": "2320",
|
||||
"account_type": "Stock Adjustment"
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Verwaltungskosten": {
|
||||
"account_number": "4997",
|
||||
@@ -340,7 +355,7 @@
|
||||
"is_group": 1,
|
||||
"Abschreibungen auf Sachanlagen (ohne AfA auf Kfz und Gebäude)": {
|
||||
"account_number": "4830",
|
||||
"account_type": "Accumulated Depreciation"
|
||||
"account_type": "Depreciation"
|
||||
},
|
||||
"Abschreibungen auf Gebäude": {
|
||||
"account_number": "4831",
|
||||
|
||||
@@ -680,10 +680,6 @@ frappe.ui.form.on("Purchase Invoice", {
|
||||
},
|
||||
|
||||
is_subcontracted: function (frm) {
|
||||
if (frm.doc.is_old_subcontracting_flow) {
|
||||
erpnext.buying.get_default_bom(frm);
|
||||
}
|
||||
|
||||
frm.toggle_reqd("supplier_warehouse", frm.doc.is_subcontracted);
|
||||
},
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_bulk_edit": 1,
|
||||
"allow_import": 1,
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2013-05-21 16:16:39",
|
||||
@@ -206,7 +207,6 @@
|
||||
"sender",
|
||||
"column_break_147",
|
||||
"inter_company_invoice_reference",
|
||||
"is_old_subcontracting_flow",
|
||||
"remarks",
|
||||
"connections_tab"
|
||||
],
|
||||
@@ -1456,14 +1456,6 @@
|
||||
"label": "Subscription",
|
||||
"options": "Subscription"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_old_subcontracting_flow",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Is Old Subcontracting Flow",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "payments_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
@@ -1703,7 +1695,7 @@
|
||||
"idx": 204,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-03-30 12:16:40.157755",
|
||||
"modified": "2026-05-04 10:10:11.717131",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
|
||||
@@ -129,7 +129,6 @@ class PurchaseInvoice(BuyingController):
|
||||
incoterm: DF.Link | None
|
||||
inter_company_invoice_reference: DF.Link | None
|
||||
is_internal_supplier: DF.Check
|
||||
is_old_subcontracting_flow: DF.Check
|
||||
is_opening: DF.Literal["No", "Yes"]
|
||||
is_paid: DF.Check
|
||||
is_return: DF.Check
|
||||
@@ -778,9 +777,6 @@ class PurchaseInvoice(BuyingController):
|
||||
self.make_bundle_using_old_serial_batch_fields()
|
||||
self.update_stock_ledger()
|
||||
|
||||
if self.is_old_subcontracting_flow:
|
||||
self.set_consumed_qty_in_subcontract_order()
|
||||
|
||||
# this sequence because outstanding may get -negative
|
||||
self.make_gl_entries()
|
||||
|
||||
@@ -1706,9 +1702,6 @@ class PurchaseInvoice(BuyingController):
|
||||
self.update_stock_ledger()
|
||||
self.delete_auto_created_batches()
|
||||
|
||||
if self.is_old_subcontracting_flow:
|
||||
self.set_consumed_qty_in_subcontract_order()
|
||||
|
||||
self.make_gl_entries_on_cancel()
|
||||
|
||||
if self.update_stock == 1:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_bulk_edit": 1,
|
||||
"autoname": "hash",
|
||||
"creation": "2013-05-22 12:43:10",
|
||||
"doctype": "DocType",
|
||||
@@ -99,7 +100,6 @@
|
||||
"service_end_date",
|
||||
"reference",
|
||||
"item_tax_rate",
|
||||
"bom",
|
||||
"include_exploded_items",
|
||||
"purchase_invoice_item",
|
||||
"col_break6",
|
||||
@@ -645,15 +645,6 @@
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.is_old_subcontracting_flow",
|
||||
"fieldname": "bom",
|
||||
"fieldtype": "Link",
|
||||
"label": "BOM",
|
||||
"options": "BOM",
|
||||
"read_only": 1,
|
||||
"read_only_depends_on": "eval:!parent.is_old_subcontracting_flow"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:parent.is_subcontracted",
|
||||
@@ -1007,7 +998,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-04-07 15:40:45.687554",
|
||||
"modified": "2026-05-04 10:48:54.535480",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice Item",
|
||||
|
||||
@@ -26,7 +26,6 @@ class PurchaseInvoiceItem(Document):
|
||||
base_rate: DF.Currency
|
||||
base_rate_with_margin: DF.Currency
|
||||
batch_no: DF.Link | None
|
||||
bom: DF.Link | None
|
||||
brand: DF.Link | None
|
||||
conversion_factor: DF.Float
|
||||
cost_center: DF.Link | None
|
||||
|
||||
@@ -12,18 +12,6 @@ erpnext.buying.setup_buying_controller();
|
||||
|
||||
frappe.ui.form.on("Purchase Order", {
|
||||
setup: function (frm) {
|
||||
if (frm.doc.is_old_subcontracting_flow) {
|
||||
frm.set_query("reserve_warehouse", "supplied_items", function () {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company,
|
||||
name: ["!=", frm.doc.supplier_warehouse],
|
||||
is_group: 0,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
frm.set_indicator_formatter("item_code", function (doc) {
|
||||
let color;
|
||||
if (!doc.qty && frm.doc.has_unit_price_items) {
|
||||
@@ -72,18 +60,6 @@ frappe.ui.form.on("Purchase Order", {
|
||||
},
|
||||
|
||||
refresh: function (frm) {
|
||||
if (frm.doc.is_old_subcontracting_flow) {
|
||||
frm.trigger("get_materials_from_supplier");
|
||||
|
||||
$("a.grey-link").each(function () {
|
||||
var id = $(this).children(":first-child").attr("data-label");
|
||||
if (id == "Duplicate") {
|
||||
$(this).remove();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (frm.doc.docstatus == 0) {
|
||||
erpnext.set_unit_price_items_note(frm);
|
||||
}
|
||||
@@ -179,7 +155,7 @@ frappe.ui.form.on("Purchase Order Item", {
|
||||
},
|
||||
|
||||
item_code: async function (frm, cdt, cdn) {
|
||||
if (frm.doc.is_subcontracted && !frm.doc.is_old_subcontracting_flow) {
|
||||
if (frm.doc.is_subcontracted) {
|
||||
var row = locals[cdt][cdn];
|
||||
|
||||
if (row.item_code && !row.fg_item) {
|
||||
@@ -227,7 +203,7 @@ frappe.ui.form.on("Purchase Order Item", {
|
||||
},
|
||||
|
||||
fg_item: async function (frm, cdt, cdn) {
|
||||
if (frm.doc.is_subcontracted && !frm.doc.is_old_subcontracting_flow) {
|
||||
if (frm.doc.is_subcontracted) {
|
||||
var row = locals[cdt][cdn];
|
||||
|
||||
if (row.fg_item) {
|
||||
@@ -248,7 +224,7 @@ frappe.ui.form.on("Purchase Order Item", {
|
||||
},
|
||||
|
||||
qty: async function (frm, cdt, cdn) {
|
||||
if (frm.doc.is_subcontracted && !frm.doc.is_old_subcontracting_flow) {
|
||||
if (frm.doc.is_subcontracted) {
|
||||
var row = locals[cdt][cdn];
|
||||
|
||||
if (row.fg_item) {
|
||||
@@ -383,26 +359,14 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
|
||||
__("Create")
|
||||
);
|
||||
if (doc.is_subcontracted) {
|
||||
if (doc.is_old_subcontracting_flow) {
|
||||
if (me.has_unsupplied_items()) {
|
||||
this.frm.add_custom_button(
|
||||
__("Material to Supplier"),
|
||||
function () {
|
||||
me.make_stock_entry();
|
||||
},
|
||||
__("Transfer")
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (!doc.items.every((item) => item.qty == item.subcontracted_qty)) {
|
||||
this.frm.add_custom_button(
|
||||
__("Subcontracting Order"),
|
||||
() => {
|
||||
me.make_subcontracting_order();
|
||||
},
|
||||
__("Create")
|
||||
);
|
||||
}
|
||||
if (!doc.items.every((item) => item.qty == item.subcontracted_qty)) {
|
||||
this.frm.add_custom_button(
|
||||
__("Subcontracting Order"),
|
||||
() => {
|
||||
me.make_subcontracting_order();
|
||||
},
|
||||
__("Create")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -748,20 +712,6 @@ cur_frm.cscript.update_status = function (label, status) {
|
||||
});
|
||||
};
|
||||
|
||||
if (cur_frm.doc.is_old_subcontracting_flow) {
|
||||
cur_frm.fields_dict["items"].grid.get_field("bom").get_query = function (doc, cdt, cdn) {
|
||||
var d = locals[cdt][cdn];
|
||||
return {
|
||||
filters: [
|
||||
["BOM", "item", "=", d.item_code],
|
||||
["BOM", "is_active", "=", "1"],
|
||||
["BOM", "docstatus", "=", "1"],
|
||||
["BOM", "company", "=", doc.company],
|
||||
],
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
function set_schedule_date(frm) {
|
||||
if (frm.doc.schedule_date) {
|
||||
erpnext.utils.copy_value_in_all_rows(
|
||||
@@ -775,9 +725,3 @@ function set_schedule_date(frm) {
|
||||
}
|
||||
|
||||
frappe.provide("erpnext.buying");
|
||||
|
||||
frappe.ui.form.on("Purchase Order", "is_subcontracted", function (frm) {
|
||||
if (frm.doc.is_old_subcontracting_flow) {
|
||||
erpnext.buying.get_default_bom(frm);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_auto_repeat": 1,
|
||||
"allow_bulk_edit": 1,
|
||||
"allow_import": 1,
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2013-05-21 16:16:39",
|
||||
@@ -164,7 +165,6 @@
|
||||
"mps",
|
||||
"is_internal_supplier",
|
||||
"inter_company_order_reference",
|
||||
"is_old_subcontracting_flow",
|
||||
"connections_tab"
|
||||
],
|
||||
"fields": [
|
||||
@@ -516,23 +516,6 @@
|
||||
"options": "Pricing Rule Detail",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"collapsible_depends_on": "supplied_items",
|
||||
"fieldname": "raw_material_details",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Raw Materials Supplied"
|
||||
},
|
||||
{
|
||||
"fieldname": "supplied_items",
|
||||
"fieldtype": "Table",
|
||||
"label": "Supplied Items",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "po_raw_material_details",
|
||||
"oldfieldtype": "Table",
|
||||
"options": "Purchase Order Item Supplied",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "sb_last_purchase",
|
||||
"fieldtype": "Section Break"
|
||||
@@ -1142,14 +1125,6 @@
|
||||
"label": "Project",
|
||||
"options": "Project"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_old_subcontracting_flow",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Is Old Subcontracting Flow",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "is_internal_supplier",
|
||||
"fieldname": "set_from_warehouse",
|
||||
@@ -1330,7 +1305,7 @@
|
||||
"idx": 105,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-03-30 12:17:43.342204",
|
||||
"modified": "2026-05-04 10:10:22.608381",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order",
|
||||
|
||||
@@ -49,9 +49,6 @@ class PurchaseOrder(BuyingController):
|
||||
PurchaseTaxesandCharges,
|
||||
)
|
||||
from erpnext.buying.doctype.purchase_order_item.purchase_order_item import PurchaseOrderItem
|
||||
from erpnext.buying.doctype.purchase_order_item_supplied.purchase_order_item_supplied import (
|
||||
PurchaseOrderItemSupplied,
|
||||
)
|
||||
|
||||
additional_discount_percentage: DF.Float
|
||||
address_display: DF.TextEditor | None
|
||||
@@ -100,7 +97,6 @@ class PurchaseOrder(BuyingController):
|
||||
incoterm: DF.Link | None
|
||||
inter_company_order_reference: DF.Link | None
|
||||
is_internal_supplier: DF.Check
|
||||
is_old_subcontracting_flow: DF.Check
|
||||
is_subcontracted: DF.Check
|
||||
item_wise_tax_details: DF.Table[ItemWiseTaxDetail]
|
||||
items: DF.Table[PurchaseOrderItem]
|
||||
@@ -147,7 +143,6 @@ class PurchaseOrder(BuyingController):
|
||||
"Closed",
|
||||
"Delivered",
|
||||
]
|
||||
supplied_items: DF.Table[PurchaseOrderItemSupplied]
|
||||
supplier: DF.Link
|
||||
supplier_address: DF.Link | None
|
||||
supplier_group: DF.Link | None
|
||||
@@ -210,14 +205,9 @@ class PurchaseOrder(BuyingController):
|
||||
self.validate_uom_is_integer("stock_uom", "stock_qty")
|
||||
|
||||
self.validate_with_previous_doc()
|
||||
self.validate_for_subcontracting()
|
||||
self.validate_minimum_order_qty()
|
||||
validate_against_blanket_order(self)
|
||||
|
||||
if self.is_old_subcontracting_flow:
|
||||
self.validate_bom_for_subcontracting_items()
|
||||
self.create_raw_materials_supplied()
|
||||
|
||||
self.validate_fg_item_for_subcontracting()
|
||||
self.set_received_qty_for_drop_ship_items()
|
||||
|
||||
@@ -328,40 +318,30 @@ class PurchaseOrder(BuyingController):
|
||||
).format(item_code, qty, itemwise_min_order_qty.get(item_code))
|
||||
)
|
||||
|
||||
def validate_bom_for_subcontracting_items(self):
|
||||
for item in self.items:
|
||||
if not item.bom:
|
||||
frappe.throw(
|
||||
_("Row #{0}: BOM is not specified for subcontracting item {0}").format(
|
||||
item.idx, item.item_code
|
||||
)
|
||||
)
|
||||
|
||||
def validate_fg_item_for_subcontracting(self):
|
||||
if self.is_subcontracted:
|
||||
if not self.is_old_subcontracting_flow:
|
||||
for item in self.items:
|
||||
if not item.fg_item:
|
||||
for item in self.items:
|
||||
if not item.fg_item:
|
||||
frappe.throw(
|
||||
_("Row #{0}: Finished Good Item is not specified for service item {1}").format(
|
||||
item.idx, item.item_code
|
||||
)
|
||||
)
|
||||
else:
|
||||
if not frappe.get_value("Item", item.fg_item, "is_sub_contracted_item"):
|
||||
frappe.throw(
|
||||
_("Row #{0}: Finished Good Item is not specified for service item {1}").format(
|
||||
item.idx, item.item_code
|
||||
_("Row #{0}: Finished Good Item {1} must be a sub-contracted item").format(
|
||||
item.idx, item.fg_item
|
||||
)
|
||||
)
|
||||
else:
|
||||
if not frappe.get_value("Item", item.fg_item, "is_sub_contracted_item"):
|
||||
frappe.throw(
|
||||
_("Row #{0}: Finished Good Item {1} must be a sub-contracted item").format(
|
||||
item.idx, item.fg_item
|
||||
)
|
||||
elif not item.bom and not frappe.get_value("Item", item.fg_item, "default_bom"):
|
||||
frappe.throw(
|
||||
_("Row #{0}: Default BOM not found for FG Item {1}").format(
|
||||
item.idx, item.fg_item
|
||||
)
|
||||
elif not item.bom and not frappe.get_value("Item", item.fg_item, "default_bom"):
|
||||
frappe.throw(
|
||||
_("Row #{0}: Default BOM not found for FG Item {1}").format(
|
||||
item.idx, item.fg_item
|
||||
)
|
||||
)
|
||||
if not item.fg_item_qty:
|
||||
frappe.throw(_("Row #{0}: Finished Good Item Qty can not be zero").format(item.idx))
|
||||
)
|
||||
if not item.fg_item_qty:
|
||||
frappe.throw(_("Row #{0}: Finished Good Item Qty can not be zero").format(item.idx))
|
||||
else:
|
||||
for item in self.items:
|
||||
item.set("fg_item", None)
|
||||
@@ -442,7 +422,6 @@ class PurchaseOrder(BuyingController):
|
||||
self.set_status(update=True, status=status)
|
||||
self.update_requested_qty()
|
||||
self.update_ordered_qty()
|
||||
self.update_reserved_qty_for_subcontract()
|
||||
self.update_subcontracting_order_status()
|
||||
self.update_blanket_order()
|
||||
self.notify_update()
|
||||
@@ -458,12 +437,11 @@ class PurchaseOrder(BuyingController):
|
||||
self.update_status_updater_if_from_pp()
|
||||
|
||||
self.update_prevdoc_status()
|
||||
if not self.is_subcontracted or self.is_old_subcontracting_flow:
|
||||
if not self.is_subcontracted:
|
||||
self.update_requested_qty()
|
||||
|
||||
self.update_ordered_qty()
|
||||
self.validate_budget()
|
||||
self.update_reserved_qty_for_subcontract()
|
||||
|
||||
frappe.get_cached_doc("Authorization Control").validate_approving_authority(
|
||||
self.doctype, self.company, self.base_grand_total
|
||||
@@ -495,7 +473,6 @@ class PurchaseOrder(BuyingController):
|
||||
if self.has_drop_ship_item():
|
||||
self.update_delivered_qty_in_sales_order()
|
||||
|
||||
self.update_reserved_qty_for_subcontract()
|
||||
self.check_on_hold_or_closed_status()
|
||||
|
||||
self.db_set("status", "Cancelled")
|
||||
@@ -504,7 +481,7 @@ class PurchaseOrder(BuyingController):
|
||||
|
||||
# Must be called after updating ordered qty in Material Request
|
||||
# bin uses Material Request Items to recalculate & update
|
||||
if not self.is_subcontracted or self.is_old_subcontracting_flow:
|
||||
if not self.is_subcontracted:
|
||||
self.update_requested_qty()
|
||||
|
||||
self.update_ordered_qty()
|
||||
@@ -581,13 +558,6 @@ class PurchaseOrder(BuyingController):
|
||||
if item.delivered_by_supplier == 1:
|
||||
item.received_qty = item.qty
|
||||
|
||||
def update_reserved_qty_for_subcontract(self):
|
||||
if self.is_old_subcontracting_flow:
|
||||
for d in self.supplied_items:
|
||||
if d.rm_item_code:
|
||||
stock_bin = get_bin(d.rm_item_code, d.reserve_warehouse)
|
||||
stock_bin.update_reserved_qty_for_sub_contracting(subcontract_doctype="Purchase Order")
|
||||
|
||||
def update_receiving_percentage(self):
|
||||
total_qty, received_qty = 0.0, 0.0
|
||||
for item in self.items:
|
||||
@@ -599,7 +569,7 @@ class PurchaseOrder(BuyingController):
|
||||
self.db_set("per_received", 0, update_modified=False)
|
||||
|
||||
def set_service_items_for_finished_goods(self):
|
||||
if not self.is_subcontracted or self.is_old_subcontracting_flow:
|
||||
if not self.is_subcontracted:
|
||||
return
|
||||
|
||||
finished_goods_without_service_item = {
|
||||
@@ -620,7 +590,7 @@ class PurchaseOrder(BuyingController):
|
||||
def can_update_items(self) -> bool:
|
||||
result = True
|
||||
|
||||
if self.is_subcontracted and not self.is_old_subcontracting_flow:
|
||||
if self.is_subcontracted:
|
||||
if frappe.db.exists(
|
||||
"Subcontracting Order", {"purchase_order": self.name, "docstatus": ["!=", 2]}
|
||||
):
|
||||
@@ -644,7 +614,7 @@ class PurchaseOrder(BuyingController):
|
||||
)
|
||||
|
||||
def auto_create_subcontracting_order(self):
|
||||
if self.is_subcontracted and not self.is_old_subcontracting_flow:
|
||||
if self.is_subcontracted:
|
||||
if frappe.db.get_single_value("Buying Settings", "auto_create_subcontracting_order"):
|
||||
make_subcontracting_order(self.name, save=True, notify=True)
|
||||
|
||||
@@ -653,7 +623,7 @@ class PurchaseOrder(BuyingController):
|
||||
update_subcontracting_order_status as update_sco_status,
|
||||
)
|
||||
|
||||
if self.is_subcontracted and not self.is_old_subcontracting_flow:
|
||||
if self.is_subcontracted:
|
||||
sco = frappe.db.get_value("Subcontracting Order", {"purchase_order": self.name, "docstatus": 1})
|
||||
|
||||
if sco:
|
||||
|
||||
@@ -1593,11 +1593,6 @@ def create_purchase_order(**args):
|
||||
po.set_missing_values()
|
||||
po.insert()
|
||||
if not args.do_not_submit:
|
||||
if po.is_subcontracted:
|
||||
supp_items = po.get("supplied_items")
|
||||
for d in supp_items:
|
||||
if not d.reserve_warehouse:
|
||||
d.reserve_warehouse = args.warehouse or "_Test Warehouse - _TC"
|
||||
po.submit()
|
||||
|
||||
return po
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_bulk_edit": 1,
|
||||
"autoname": "hash",
|
||||
"creation": "2024-12-09 12:54:24.652161",
|
||||
"doctype": "DocType",
|
||||
@@ -94,7 +95,6 @@
|
||||
"manufacturer_part_no",
|
||||
"column_break_14",
|
||||
"bom",
|
||||
"include_exploded_items",
|
||||
"item_weight_details",
|
||||
"weight_per_unit",
|
||||
"total_weight",
|
||||
@@ -588,22 +588,12 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.is_old_subcontracting_flow",
|
||||
"fieldname": "bom",
|
||||
"fieldtype": "Link",
|
||||
"label": "BOM",
|
||||
"options": "BOM",
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"read_only_depends_on": "eval:!parent.is_old_subcontracting_flow"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:parent.is_old_subcontracting_flow",
|
||||
"fieldname": "include_exploded_items",
|
||||
"fieldtype": "Check",
|
||||
"label": "Include Exploded Items",
|
||||
"print_hide": 1
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_56",
|
||||
@@ -870,20 +860,20 @@
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.is_subcontracted && !parent.is_old_subcontracting_flow",
|
||||
"depends_on": "eval:parent.is_subcontracted",
|
||||
"fieldname": "fg_item",
|
||||
"fieldtype": "Link",
|
||||
"label": "Finished Good",
|
||||
"mandatory_depends_on": "eval:parent.is_subcontracted && !parent.is_old_subcontracting_flow",
|
||||
"mandatory_depends_on": "eval:parent.is_subcontracted",
|
||||
"options": "Item"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"depends_on": "eval:parent.is_subcontracted && !parent.is_old_subcontracting_flow",
|
||||
"depends_on": "eval:parent.is_subcontracted",
|
||||
"fieldname": "fg_item_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Finished Good Qty",
|
||||
"mandatory_depends_on": "eval:parent.is_subcontracted && !parent.is_old_subcontracting_flow",
|
||||
"mandatory_depends_on": "eval:parent.is_subcontracted",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
@@ -935,7 +925,7 @@
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"default": "0",
|
||||
"depends_on": "eval:parent.is_subcontracted && !parent.is_old_subcontracting_flow",
|
||||
"depends_on": "eval:parent.is_subcontracted",
|
||||
"fieldname": "subcontracted_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Subcontracted Quantity",
|
||||
@@ -950,7 +940,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-11-30 16:51:57.761673",
|
||||
"modified": "2026-05-04 10:51:05.183490",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order Item",
|
||||
|
||||
@@ -27,7 +27,6 @@ class PurchaseOrderItem(Document):
|
||||
billed_amt: DF.Currency
|
||||
blanket_order: DF.Link | None
|
||||
blanket_order_rate: DF.Currency
|
||||
bom: DF.Link | None
|
||||
brand: DF.Link | None
|
||||
company_total_stock: DF.Float
|
||||
conversion_factor: DF.Float
|
||||
@@ -43,7 +42,6 @@ class PurchaseOrderItem(Document):
|
||||
fg_item_qty: DF.Float
|
||||
from_warehouse: DF.Link | None
|
||||
image: DF.Attach | None
|
||||
include_exploded_items: DF.Check
|
||||
is_fixed_asset: DF.Check
|
||||
is_free_item: DF.Check
|
||||
item_code: DF.Link
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
Raw material (if any) supplied along with Purchase Order.
|
||||
@@ -1,195 +0,0 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2013-02-22 01:27:42",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"main_item_code",
|
||||
"rm_item_code",
|
||||
"column_break_3",
|
||||
"stock_uom",
|
||||
"reserve_warehouse",
|
||||
"conversion_factor",
|
||||
"column_break_6",
|
||||
"bom_detail_no",
|
||||
"reference_name",
|
||||
"section_break2",
|
||||
"rate",
|
||||
"col_break2",
|
||||
"amount",
|
||||
"section_break1",
|
||||
"required_qty",
|
||||
"supplied_qty",
|
||||
"col_break1",
|
||||
"consumed_qty",
|
||||
"returned_qty",
|
||||
"total_supplied_qty"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"columns": 2,
|
||||
"fieldname": "main_item_code",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Item Code",
|
||||
"oldfieldname": "main_item_code",
|
||||
"oldfieldtype": "Data",
|
||||
"options": "Item",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"columns": 2,
|
||||
"fieldname": "rm_item_code",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Raw Material Item Code",
|
||||
"oldfieldname": "rm_item_code",
|
||||
"oldfieldtype": "Data",
|
||||
"options": "Item",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"columns": 2,
|
||||
"fieldname": "required_qty",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Required Qty",
|
||||
"oldfieldname": "required_qty",
|
||||
"oldfieldtype": "Currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"columns": 2,
|
||||
"fieldname": "rate",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Rate",
|
||||
"oldfieldname": "rate",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "Company:company:default_currency"
|
||||
},
|
||||
{
|
||||
"fieldname": "amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Amount",
|
||||
"oldfieldname": "amount",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_6",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "bom_detail_no",
|
||||
"fieldtype": "Data",
|
||||
"label": "BOM Detail No",
|
||||
"oldfieldname": "bom_detail_no",
|
||||
"oldfieldtype": "Data",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "reference_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Reference Name",
|
||||
"oldfieldname": "reference_name",
|
||||
"oldfieldtype": "Data",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "conversion_factor",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 1,
|
||||
"label": "Conversion Factor",
|
||||
"oldfieldname": "conversion_factor",
|
||||
"oldfieldtype": "Currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "stock_uom",
|
||||
"fieldtype": "Link",
|
||||
"label": "Stock Uom",
|
||||
"oldfieldname": "stock_uom",
|
||||
"oldfieldtype": "Data",
|
||||
"options": "UOM",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"columns": 2,
|
||||
"fieldname": "reserve_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Reserve Warehouse",
|
||||
"options": "Warehouse"
|
||||
},
|
||||
{
|
||||
"fieldname": "supplied_qty",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Supplied Qty",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break1",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "col_break1",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break2",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "col_break2",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "consumed_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Consumed Qty",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "returned_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Returned Qty",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "total_supplied_qty",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 1,
|
||||
"label": "Total Supplied Qty",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:10:25.280016",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order Item Supplied",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class PurchaseOrderItemSupplied(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
amount: DF.Currency
|
||||
bom_detail_no: DF.Data | None
|
||||
consumed_qty: DF.Float
|
||||
conversion_factor: DF.Float
|
||||
main_item_code: DF.Link | None
|
||||
parent: DF.Data
|
||||
parentfield: DF.Data
|
||||
parenttype: DF.Data
|
||||
rate: DF.Currency
|
||||
reference_name: DF.Data | None
|
||||
required_qty: DF.Float
|
||||
reserve_warehouse: DF.Link | None
|
||||
returned_qty: DF.Float
|
||||
rm_item_code: DF.Link | None
|
||||
stock_uom: DF.Link | None
|
||||
supplied_qty: DF.Float
|
||||
total_supplied_qty: DF.Float
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
@@ -25,13 +25,6 @@ frappe.query_reports["Subcontract Order Summary"] = {
|
||||
default: frappe.datetime.get_today(),
|
||||
reqd: 1,
|
||||
},
|
||||
{
|
||||
label: __("Order Type"),
|
||||
fieldname: "order_type",
|
||||
fieldtype: "Select",
|
||||
options: ["Purchase Order", "Subcontracting Order"],
|
||||
default: "Subcontracting Order",
|
||||
},
|
||||
{
|
||||
label: __("Subcontract Order"),
|
||||
fieldname: "name",
|
||||
|
||||
@@ -28,37 +28,34 @@ def get_data(report_filters):
|
||||
|
||||
def get_subcontracted_orders(report_filters):
|
||||
fields = [
|
||||
f"`tab{report_filters.order_type} Item`.`parent` as order_id",
|
||||
f"`tab{report_filters.order_type} Item`.`item_code`",
|
||||
f"`tab{report_filters.order_type} Item`.`item_name`",
|
||||
f"`tab{report_filters.order_type} Item`.`qty`",
|
||||
f"`tab{report_filters.order_type} Item`.`name`",
|
||||
f"`tab{report_filters.order_type} Item`.`received_qty`",
|
||||
f"`tab{report_filters.order_type}`.`status`",
|
||||
"`tabSubcontracting Order Item`.`parent` as order_id",
|
||||
"`tabSubcontracting Order Item`.`item_code`",
|
||||
"`tabSubcontracting Order Item`.`item_name`",
|
||||
"`tabSubcontracting Order Item`.`qty`",
|
||||
"`tabSubcontracting Order Item`.`name`",
|
||||
"`tabSubcontracting Order Item`.`received_qty`",
|
||||
"`tabSubcontracting Order`.`status`",
|
||||
]
|
||||
|
||||
filters = get_filters(report_filters)
|
||||
|
||||
return frappe.get_all(report_filters.order_type, fields=fields, filters=filters) or []
|
||||
return frappe.get_all("Subcontracting Order", fields=fields, filters=filters) or []
|
||||
|
||||
|
||||
def get_filters(report_filters):
|
||||
filters = [
|
||||
[report_filters.order_type, "docstatus", "=", 1],
|
||||
["Subcontracting Order", "docstatus", "=", 1],
|
||||
[
|
||||
report_filters.order_type,
|
||||
"Subcontracting Order",
|
||||
"transaction_date",
|
||||
"between",
|
||||
(report_filters.from_date, report_filters.to_date),
|
||||
],
|
||||
]
|
||||
|
||||
if report_filters.order_type == "Purchase Order":
|
||||
filters.append(["Purchase Order", "is_old_subcontracting_flow", "=", 1])
|
||||
|
||||
for field in ["name", "company"]:
|
||||
if report_filters.get(field):
|
||||
filters.append([report_filters.order_type, field, "=", report_filters.get(field)])
|
||||
filters.append(["Subcontracting Order", field, "=", report_filters.get(field)])
|
||||
|
||||
return filters
|
||||
|
||||
@@ -82,12 +79,7 @@ def get_supplied_items(orders, report_filters):
|
||||
filters = {"parent": ("in", [d.order_id for d in orders]), "docstatus": 1}
|
||||
|
||||
supplied_items = {}
|
||||
supplied_items_table = (
|
||||
"Purchase Order Item Supplied"
|
||||
if report_filters.order_type == "Purchase Order"
|
||||
else "Subcontracting Order Supplied Item"
|
||||
)
|
||||
for row in frappe.get_all(supplied_items_table, fields=fields, filters=filters):
|
||||
for row in frappe.get_all("Subcontracting Order Supplied Item", fields=fields, filters=filters):
|
||||
new_key = (row.parent, row.reference_name, row.main_item_code)
|
||||
|
||||
supplied_items.setdefault(new_key, []).append(row)
|
||||
@@ -128,7 +120,7 @@ def get_columns(filters):
|
||||
"label": _("Subcontract Order"),
|
||||
"fieldname": "order_id",
|
||||
"fieldtype": "Link",
|
||||
"options": filters.order_type,
|
||||
"options": "Subcontracting Order",
|
||||
"width": 100,
|
||||
},
|
||||
{"label": _("Status"), "fieldname": "status", "fieldtype": "Data", "width": 80},
|
||||
|
||||
@@ -3,13 +3,6 @@
|
||||
|
||||
frappe.query_reports["Subcontracted Item To Be Received"] = {
|
||||
filters: [
|
||||
{
|
||||
label: __("Order Type"),
|
||||
fieldname: "order_type",
|
||||
fieldtype: "Select",
|
||||
options: ["Purchase Order", "Subcontracting Order"],
|
||||
default: "Subcontracting Order",
|
||||
},
|
||||
{
|
||||
fieldname: "supplier",
|
||||
label: __("Supplier"),
|
||||
|
||||
@@ -22,7 +22,6 @@ def get_columns(filters):
|
||||
"label": _("Subcontract Order"),
|
||||
"fieldtype": "Link",
|
||||
"fieldname": "subcontract_order",
|
||||
"options": filters.order_type,
|
||||
"width": 150,
|
||||
},
|
||||
{"label": _("Date"), "fieldtype": "Date", "fieldname": "date", "hidden": 1, "width": 150},
|
||||
@@ -59,7 +58,7 @@ def get_columns(filters):
|
||||
def get_data(data, filters):
|
||||
orders = get_subcontract_orders(filters)
|
||||
orders_name = [order.name for order in orders]
|
||||
subcontracted_items = get_subcontract_order_supplied_item(filters.order_type, orders_name)
|
||||
subcontracted_items = get_subcontract_order_supplied_item("Subcontracting Order", orders_name)
|
||||
for item in subcontracted_items:
|
||||
for order in orders:
|
||||
if order.name == item.parent and item.received_qty < item.qty:
|
||||
@@ -84,11 +83,8 @@ def get_subcontract_orders(filters):
|
||||
["docstatus", "=", 1],
|
||||
]
|
||||
|
||||
if filters.order_type == "Purchase Order":
|
||||
record_filters.append(["is_old_subcontracting_flow", "=", 1])
|
||||
|
||||
return frappe.get_all(
|
||||
filters.order_type, filters=record_filters, fields=["name", "transaction_date", "supplier"]
|
||||
"Subcontracting Order", filters=record_filters, fields=["name", "transaction_date", "supplier"]
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -3,13 +3,6 @@
|
||||
|
||||
frappe.query_reports["Subcontracted Raw Materials To Be Transferred"] = {
|
||||
filters: [
|
||||
{
|
||||
label: __("Order Type"),
|
||||
fieldname: "order_type",
|
||||
fieldtype: "Select",
|
||||
options: ["Purchase Order", "Subcontracting Order"],
|
||||
default: "Subcontracting Order",
|
||||
},
|
||||
{
|
||||
fieldname: "supplier",
|
||||
label: __("Supplier"),
|
||||
|
||||
@@ -22,7 +22,6 @@ def get_columns(filters):
|
||||
"label": _("Subcontract Order"),
|
||||
"fieldtype": "Link",
|
||||
"fieldname": "subcontract_order",
|
||||
"options": filters.order_type,
|
||||
"width": 200,
|
||||
},
|
||||
{"label": _("Date"), "fieldtype": "Date", "fieldname": "date", "width": 150},
|
||||
@@ -61,25 +60,18 @@ def get_data(filters):
|
||||
|
||||
|
||||
def get_order_items_to_supply(filters):
|
||||
supplied_items_table = (
|
||||
"Purchase Order Item Supplied"
|
||||
if filters.order_type == "Purchase Order"
|
||||
else "Subcontracting Order Supplied Item"
|
||||
)
|
||||
supplied_items_table = "Subcontracting Order Supplied Item"
|
||||
|
||||
record_filters = [
|
||||
[filters.order_type, "per_received", "<", "100"],
|
||||
[filters.order_type, "supplier", "=", filters.supplier],
|
||||
[filters.order_type, "transaction_date", "<=", filters.to_date],
|
||||
[filters.order_type, "transaction_date", ">=", filters.from_date],
|
||||
[filters.order_type, "docstatus", "=", 1],
|
||||
["Subcontracting Order", "per_received", "<", "100"],
|
||||
["Subcontracting Order", "supplier", "=", filters.supplier],
|
||||
["Subcontracting Order", "transaction_date", "<=", filters.to_date],
|
||||
["Subcontracting Order", "transaction_date", ">=", filters.from_date],
|
||||
["Subcontracting Order", "docstatus", "=", 1],
|
||||
]
|
||||
|
||||
if filters.order_type == "Purchase Order":
|
||||
record_filters.append([filters.order_type, "is_old_subcontracting_flow", "=", 1])
|
||||
|
||||
return frappe.db.get_all(
|
||||
filters.order_type,
|
||||
"Subcontracting Order",
|
||||
fields=[
|
||||
"name as subcontract_order",
|
||||
"transaction_date as date",
|
||||
|
||||
@@ -3856,6 +3856,137 @@ def validate_and_delete_children(parent, data, ordered_item=None) -> bool:
|
||||
return bool(deleted_children)
|
||||
|
||||
|
||||
def get_allow_zero_qty(parent_doctype: str) -> bool:
|
||||
if parent_doctype == "Sales Order":
|
||||
return frappe.db.get_single_value("Selling Settings", "allow_zero_qty_in_sales_order") or False
|
||||
if parent_doctype == "Purchase Order":
|
||||
return frappe.db.get_single_value("Buying Settings", "allow_zero_qty_in_purchase_order") or False
|
||||
return False
|
||||
|
||||
|
||||
def get_child_item_change_state(parent_doctype: str, child_item, new_data) -> frappe._dict:
|
||||
prev_rate, new_rate = flt(child_item.get("rate")), flt(new_data.get("rate"))
|
||||
prev_qty, new_qty = flt(child_item.get("qty")), flt(new_data.get("qty"))
|
||||
prev_fg_qty, new_fg_qty = flt(child_item.get("fg_item_qty")), flt(new_data.get("fg_item_qty"))
|
||||
prev_con_fac, new_con_fac = (
|
||||
flt(child_item.get("conversion_factor")),
|
||||
flt(new_data.get("conversion_factor")),
|
||||
)
|
||||
|
||||
if parent_doctype == "Sales Order":
|
||||
prev_date, new_date = child_item.get("delivery_date"), new_data.get("delivery_date")
|
||||
elif parent_doctype == "Purchase Order":
|
||||
prev_date, new_date = child_item.get("schedule_date"), new_data.get("schedule_date")
|
||||
else:
|
||||
prev_date, new_date = None, None
|
||||
|
||||
if parent_doctype in ["Quotation", "Supplier Quotation"]:
|
||||
date_unchanged = False
|
||||
else:
|
||||
prev_date = getdate(prev_date) if prev_date else None
|
||||
new_date = getdate(new_date) if new_date else None
|
||||
date_unchanged = prev_date == new_date
|
||||
|
||||
return frappe._dict(
|
||||
rate_unchanged=prev_rate == new_rate,
|
||||
qty_unchanged=prev_qty == new_qty,
|
||||
fg_qty_unchanged=prev_fg_qty == new_fg_qty,
|
||||
uom_unchanged=child_item.get("uom") == new_data.get("uom"),
|
||||
conversion_factor_unchanged=prev_con_fac == new_con_fac,
|
||||
date_unchanged=date_unchanged,
|
||||
description_unchanged=child_item.get("description") == new_data.get("description"),
|
||||
)
|
||||
|
||||
|
||||
def is_child_item_unchanged(change_state: frappe._dict) -> bool:
|
||||
return (
|
||||
change_state.rate_unchanged
|
||||
and change_state.qty_unchanged
|
||||
and change_state.fg_qty_unchanged
|
||||
and change_state.conversion_factor_unchanged
|
||||
and change_state.uom_unchanged
|
||||
and change_state.date_unchanged
|
||||
and change_state.description_unchanged
|
||||
)
|
||||
|
||||
|
||||
def update_child_item_rate_and_discount(
|
||||
parent_doctype: str, child_item, new_data, allow_zero_qty: bool, rate_unchanged: bool | None = None
|
||||
) -> None:
|
||||
rate_precision = child_item.precision("rate") or 2
|
||||
qty_precision = child_item.precision("qty") or 2
|
||||
|
||||
if rate_unchanged is None:
|
||||
prev_rate, new_rate = flt(child_item.get("rate")), flt(new_data.get("rate"))
|
||||
rate_unchanged = prev_rate == new_rate
|
||||
|
||||
if not rate_unchanged and not child_item.get("qty") and allow_zero_qty:
|
||||
frappe.throw(_("Rate of '{}' items cannot be changed").format(frappe.bold(_("Unit Price"))))
|
||||
|
||||
# Amount cannot be lesser than billed amount, except for negative amounts
|
||||
row_rate = flt(new_data.get("rate"), rate_precision)
|
||||
|
||||
if parent_doctype in ["Purchase Order", "Sales Order"]:
|
||||
amount_below_billed_amt = flt(child_item.billed_amt, rate_precision) > flt(
|
||||
row_rate * flt(new_data.get("qty"), qty_precision), rate_precision
|
||||
)
|
||||
if amount_below_billed_amt and row_rate > 0.0:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Row #{0}: Cannot set Rate if the billed amount is greater than the amount for Item {1}."
|
||||
).format(child_item.idx, child_item.item_code)
|
||||
)
|
||||
|
||||
child_item.rate = row_rate
|
||||
|
||||
if parent_doctype not in ["Sales Order", "Purchase Order"] or not flt(child_item.price_list_rate):
|
||||
return
|
||||
|
||||
if flt(child_item.rate) > flt(child_item.price_list_rate):
|
||||
# if rate is greater than price_list_rate, set margin or set discount
|
||||
child_item.discount_percentage = 0
|
||||
child_item.margin_type = "Amount"
|
||||
child_item.margin_rate_or_amount = flt(
|
||||
child_item.rate - child_item.price_list_rate,
|
||||
child_item.precision("margin_rate_or_amount"),
|
||||
)
|
||||
child_item.rate_with_margin = child_item.rate
|
||||
else:
|
||||
child_item.discount_percentage = flt(
|
||||
(1 - flt(child_item.rate) / flt(child_item.price_list_rate)) * 100.0,
|
||||
child_item.precision("discount_percentage"),
|
||||
)
|
||||
child_item.discount_amount = flt(child_item.price_list_rate) - flt(child_item.rate)
|
||||
child_item.margin_type = ""
|
||||
child_item.margin_rate_or_amount = 0
|
||||
child_item.rate_with_margin = 0
|
||||
|
||||
|
||||
def update_child_item_uom_and_weight(child_item, new_data) -> None:
|
||||
conv_fac_precision = child_item.precision("conversion_factor") or 2
|
||||
|
||||
if new_data.get("conversion_factor"):
|
||||
if child_item.stock_uom == child_item.uom:
|
||||
child_item.conversion_factor = 1
|
||||
else:
|
||||
child_item.conversion_factor = flt(new_data.get("conversion_factor"), conv_fac_precision)
|
||||
|
||||
if new_data.get("uom"):
|
||||
child_item.uom = new_data.get("uom")
|
||||
conversion_factor = flt(
|
||||
get_conversion_factor(child_item.item_code, child_item.uom).get("conversion_factor")
|
||||
)
|
||||
child_item.conversion_factor = (
|
||||
flt(new_data.get("conversion_factor"), conv_fac_precision) or conversion_factor
|
||||
)
|
||||
|
||||
if child_item.get("total_weight") and child_item.get("weight_per_unit"):
|
||||
child_item.total_weight = flt(
|
||||
child_item.weight_per_unit * child_item.qty * child_item.conversion_factor,
|
||||
child_item.precision("total_weight"),
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_child_qty_rate(
|
||||
parent_doctype: str, trans_items: str, parent_doctype_name: str, child_docname: str = "items"
|
||||
@@ -3904,15 +4035,8 @@ def update_child_qty_rate(
|
||||
child_doctype = parent_doctype + " Item"
|
||||
return set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child_docname, item_row)
|
||||
|
||||
def is_allowed_zero_qty():
|
||||
if parent_doctype == "Sales Order":
|
||||
return frappe.db.get_single_value("Selling Settings", "allow_zero_qty_in_sales_order") or False
|
||||
elif parent_doctype == "Purchase Order":
|
||||
return frappe.db.get_single_value("Buying Settings", "allow_zero_qty_in_purchase_order") or False
|
||||
return False
|
||||
|
||||
def validate_quantity_and_rate(child_item, new_data):
|
||||
if not flt(new_data.get("qty")) and not is_allowed_zero_qty():
|
||||
if not flt(new_data.get("qty")) and not allow_zero_qty:
|
||||
frappe.throw(
|
||||
_("Row #{0}:Quantity for Item {1} cannot be zero.").format(
|
||||
new_data.get("idx"), frappe.bold(new_data.get("item_code"))
|
||||
@@ -4004,6 +4128,7 @@ def update_child_qty_rate(
|
||||
any_conversion_factor_changed = False
|
||||
|
||||
parent = frappe.get_doc(parent_doctype, parent_doctype_name)
|
||||
allow_zero_qty = get_allow_zero_qty(parent_doctype)
|
||||
|
||||
check_doc_permissions(parent, "write")
|
||||
|
||||
@@ -4020,6 +4145,7 @@ def update_child_qty_rate(
|
||||
|
||||
for d in data:
|
||||
new_child_flag = False
|
||||
rate_unchanged = None
|
||||
|
||||
if not d.get("item_code"):
|
||||
# ignore empty rows
|
||||
@@ -4034,42 +4160,10 @@ def update_child_qty_rate(
|
||||
check_doc_permissions(parent, "write")
|
||||
child_item = frappe.get_doc(parent_doctype + " Item", d.get("docname"))
|
||||
|
||||
prev_rate, new_rate = flt(child_item.get("rate")), flt(d.get("rate"))
|
||||
prev_qty, new_qty = flt(child_item.get("qty")), flt(d.get("qty"))
|
||||
prev_fg_qty, new_fg_qty = flt(child_item.get("fg_item_qty")), flt(d.get("fg_item_qty"))
|
||||
prev_con_fac, new_con_fac = (
|
||||
flt(child_item.get("conversion_factor")),
|
||||
flt(d.get("conversion_factor")),
|
||||
)
|
||||
prev_uom, new_uom = child_item.get("uom"), d.get("uom")
|
||||
|
||||
if parent_doctype == "Sales Order":
|
||||
prev_date, new_date = child_item.get("delivery_date"), d.get("delivery_date")
|
||||
elif parent_doctype == "Purchase Order":
|
||||
prev_date, new_date = child_item.get("schedule_date"), d.get("schedule_date")
|
||||
|
||||
prev_description, new_description = (child_item.get("description"), d.get("description"))
|
||||
description_unchanged = prev_description == new_description
|
||||
rate_unchanged = prev_rate == new_rate
|
||||
qty_unchanged = prev_qty == new_qty
|
||||
fg_qty_unchanged = prev_fg_qty == new_fg_qty
|
||||
uom_unchanged = prev_uom == new_uom
|
||||
conversion_factor_unchanged = prev_con_fac == new_con_fac
|
||||
any_conversion_factor_changed |= not conversion_factor_unchanged
|
||||
date_unchanged = (
|
||||
(prev_date == getdate(new_date) if prev_date and new_date else False)
|
||||
if parent_doctype not in ["Quotation", "Supplier Quotation"]
|
||||
else None
|
||||
) # in case of delivery note etc
|
||||
if (
|
||||
rate_unchanged
|
||||
and qty_unchanged
|
||||
and fg_qty_unchanged
|
||||
and conversion_factor_unchanged
|
||||
and uom_unchanged
|
||||
and date_unchanged
|
||||
and description_unchanged
|
||||
):
|
||||
change_state = get_child_item_change_state(parent_doctype, child_item, d)
|
||||
rate_unchanged = change_state.rate_unchanged
|
||||
any_conversion_factor_changed |= not change_state.conversion_factor_unchanged
|
||||
if is_child_item_unchanged(change_state):
|
||||
continue
|
||||
|
||||
validate_quantity_and_rate(child_item, d)
|
||||
@@ -4077,11 +4171,7 @@ def update_child_qty_rate(
|
||||
if flt(child_item.get("qty")) != flt(d.get("qty")):
|
||||
any_qty_changed = True
|
||||
|
||||
if (
|
||||
parent.doctype in ["Sales Order", "Purchase Order"]
|
||||
and parent.is_subcontracted
|
||||
and not parent.get("is_old_subcontracting_flow")
|
||||
):
|
||||
if parent.doctype in ["Sales Order", "Purchase Order"] and parent.is_subcontracted:
|
||||
validate_fg_item_for_subcontracting(d, new_child_flag)
|
||||
child_item.fg_item_qty = flt(d["fg_item_qty"])
|
||||
|
||||
@@ -4090,52 +4180,10 @@ def update_child_qty_rate(
|
||||
|
||||
child_item.qty = flt(d.get("qty"))
|
||||
child_item.description = d.get("description")
|
||||
rate_precision = child_item.precision("rate") or 2
|
||||
conv_fac_precision = child_item.precision("conversion_factor") or 2
|
||||
qty_precision = child_item.precision("qty") or 2
|
||||
|
||||
prev_rate, new_rate = flt(child_item.get("rate")), flt(d.get("rate"))
|
||||
rate_unchanged = prev_rate == new_rate
|
||||
if not rate_unchanged and not child_item.get("qty") and is_allowed_zero_qty():
|
||||
frappe.throw(_("Rate of '{}' items cannot be changed").format(frappe.bold(_("Unit Price"))))
|
||||
# Amount cannot be lesser than billed amount, except for negative amounts
|
||||
row_rate = flt(d.get("rate"), rate_precision)
|
||||
|
||||
if parent_doctype in ["Purchase Order", "Sales Order"]:
|
||||
amount_below_billed_amt = flt(child_item.billed_amt, rate_precision) > flt(
|
||||
row_rate * flt(d.get("qty"), qty_precision), rate_precision
|
||||
)
|
||||
if amount_below_billed_amt and row_rate > 0.0:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Row #{0}: Cannot set Rate if the billed amount is greater than the amount for Item {1}."
|
||||
).format(child_item.idx, child_item.item_code)
|
||||
)
|
||||
else:
|
||||
child_item.rate = row_rate
|
||||
else:
|
||||
child_item.rate = row_rate
|
||||
|
||||
if d.get("conversion_factor"):
|
||||
if child_item.stock_uom == child_item.uom:
|
||||
child_item.conversion_factor = 1
|
||||
else:
|
||||
child_item.conversion_factor = flt(d.get("conversion_factor"), conv_fac_precision)
|
||||
|
||||
if d.get("uom"):
|
||||
child_item.uom = d.get("uom")
|
||||
conversion_factor = flt(
|
||||
get_conversion_factor(child_item.item_code, child_item.uom).get("conversion_factor")
|
||||
)
|
||||
child_item.conversion_factor = (
|
||||
flt(d.get("conversion_factor"), conv_fac_precision) or conversion_factor
|
||||
)
|
||||
|
||||
if child_item.get("total_weight") and child_item.get("weight_per_unit"):
|
||||
child_item.total_weight = flt(
|
||||
child_item.weight_per_unit * child_item.qty * child_item.conversion_factor,
|
||||
child_item.precision("total_weight"),
|
||||
)
|
||||
update_child_item_rate_and_discount(
|
||||
parent_doctype, child_item, d, allow_zero_qty, rate_unchanged=rate_unchanged
|
||||
)
|
||||
update_child_item_uom_and_weight(child_item, d)
|
||||
|
||||
if d.get("delivery_date") and parent_doctype == "Sales Order":
|
||||
child_item.delivery_date = d.get("delivery_date")
|
||||
@@ -4146,28 +4194,6 @@ def update_child_qty_rate(
|
||||
if d.get("bom_no") and parent_doctype == "Sales Order":
|
||||
child_item.bom_no = d.get("bom_no")
|
||||
|
||||
if parent_doctype in ["Sales Order", "Purchase Order"]:
|
||||
if flt(child_item.price_list_rate):
|
||||
if flt(child_item.rate) > flt(child_item.price_list_rate):
|
||||
# if rate is greater than price_list_rate, set margin
|
||||
# or set discount
|
||||
child_item.discount_percentage = 0
|
||||
child_item.margin_type = "Amount"
|
||||
child_item.margin_rate_or_amount = flt(
|
||||
child_item.rate - child_item.price_list_rate,
|
||||
child_item.precision("margin_rate_or_amount"),
|
||||
)
|
||||
child_item.rate_with_margin = child_item.rate
|
||||
else:
|
||||
child_item.discount_percentage = flt(
|
||||
(1 - flt(child_item.rate) / flt(child_item.price_list_rate)) * 100.0,
|
||||
child_item.precision("discount_percentage"),
|
||||
)
|
||||
child_item.discount_amount = flt(child_item.price_list_rate) - flt(child_item.rate)
|
||||
child_item.margin_type = ""
|
||||
child_item.margin_rate_or_amount = 0
|
||||
child_item.rate_with_margin = 0
|
||||
|
||||
child_item.flags.ignore_validate_update_after_submit = True
|
||||
if new_child_flag:
|
||||
parent.load_from_db()
|
||||
@@ -4216,18 +4242,12 @@ def update_child_qty_rate(
|
||||
parent.update_receiving_percentage()
|
||||
|
||||
if parent.is_subcontracted:
|
||||
if parent.is_old_subcontracting_flow:
|
||||
if should_update_supplied_items(parent):
|
||||
parent.update_reserved_qty_for_subcontract()
|
||||
parent.create_raw_materials_supplied()
|
||||
parent.save()
|
||||
else:
|
||||
if not parent.can_update_items():
|
||||
frappe.throw(
|
||||
_(
|
||||
"Items cannot be updated as Subcontracting Order is created against the Purchase Order {0}."
|
||||
).format(frappe.bold(parent.name))
|
||||
)
|
||||
if not parent.can_update_items():
|
||||
frappe.throw(
|
||||
_(
|
||||
"Items cannot be updated as Subcontracting Order is created against the Purchase Order {0}."
|
||||
).format(frappe.bold(parent.name))
|
||||
)
|
||||
elif parent_doctype == "Sales Order": # Sales Order
|
||||
if parent.is_subcontracted and not parent.can_update_items():
|
||||
frappe.throw(
|
||||
|
||||
@@ -59,11 +59,6 @@ class BuyingController(SubcontractingController):
|
||||
self.validate_rejected_warehouse()
|
||||
self.validate_accepted_rejected_qty()
|
||||
validate_for_items(self)
|
||||
|
||||
# sub-contracting
|
||||
self.validate_for_subcontracting()
|
||||
if self.get("is_old_subcontracting_flow"):
|
||||
self.create_raw_materials_supplied()
|
||||
self.set_landed_cost_voucher_amount()
|
||||
|
||||
if self.doctype in ("Purchase Receipt", "Purchase Invoice"):
|
||||
@@ -488,21 +483,12 @@ class BuyingController(SubcontractingController):
|
||||
if not qty_in_stock_uom and item.get("rejected_qty"):
|
||||
qty_in_stock_uom = flt(item.rejected_qty * item.conversion_factor)
|
||||
|
||||
if self.get("is_old_subcontracting_flow"):
|
||||
item.rm_supp_cost = self.get_supplied_items_cost(item.name, reset_outgoing_rate)
|
||||
item.valuation_rate = (
|
||||
net_rate
|
||||
+ item.item_tax_amount
|
||||
+ item.rm_supp_cost
|
||||
+ flt(item.landed_cost_voucher_amount)
|
||||
) / qty_in_stock_uom
|
||||
else:
|
||||
item.valuation_rate = (
|
||||
net_rate
|
||||
+ item.item_tax_amount
|
||||
+ flt(item.landed_cost_voucher_amount)
|
||||
+ flt(item.get("amount_difference_with_purchase_invoice"))
|
||||
) / qty_in_stock_uom
|
||||
item.valuation_rate = (
|
||||
net_rate
|
||||
+ item.item_tax_amount
|
||||
+ flt(item.landed_cost_voucher_amount)
|
||||
+ flt(item.get("amount_difference_with_purchase_invoice"))
|
||||
) / qty_in_stock_uom
|
||||
else:
|
||||
item.valuation_rate = 0.0
|
||||
|
||||
@@ -642,36 +628,6 @@ class BuyingController(SubcontractingController):
|
||||
* (d.conversion_factor or 1)
|
||||
)
|
||||
|
||||
def validate_for_subcontracting(self):
|
||||
if self.is_subcontracted and self.get("is_old_subcontracting_flow"):
|
||||
if self.doctype in ["Purchase Receipt", "Purchase Invoice"] and not self.supplier_warehouse:
|
||||
frappe.throw(
|
||||
_("{field_label} is mandatory for sub-contracted {doctype}.").format(
|
||||
field_label=_(self.meta.get_label("supplier_warehouse")), doctype=_(self.doctype)
|
||||
)
|
||||
)
|
||||
|
||||
for item in self.get("items"):
|
||||
if item in self.sub_contracted_items and not item.bom:
|
||||
frappe.throw(
|
||||
_("Please select BOM in BOM field for Item {item_code}.").format(
|
||||
item_code=frappe.bold(item.item_code)
|
||||
)
|
||||
)
|
||||
if self.doctype != "Purchase Order":
|
||||
return
|
||||
for row in self.get("supplied_items"):
|
||||
if not row.reserve_warehouse:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Reserved Warehouse is mandatory for the Item {item_code} in Raw Materials supplied."
|
||||
).format(item_code=frappe.bold(row.rm_item_code))
|
||||
)
|
||||
else:
|
||||
for item in self.get("items"):
|
||||
if item.get("bom"):
|
||||
item.bom = None
|
||||
|
||||
def set_qty_as_per_stock_uom(self):
|
||||
allow_to_edit_stock_qty = frappe.get_single_value(
|
||||
"Stock Settings", "allow_to_edit_stock_uom_qty_for_purchase"
|
||||
@@ -885,9 +841,6 @@ class BuyingController(SubcontractingController):
|
||||
)
|
||||
)
|
||||
|
||||
if self.get("is_old_subcontracting_flow"):
|
||||
self.make_sl_entries_for_supplier_warehouse(sl_entries)
|
||||
|
||||
self.make_sl_entries(
|
||||
sl_entries,
|
||||
allow_negative_stock=allow_negative_stock,
|
||||
@@ -943,8 +896,6 @@ class BuyingController(SubcontractingController):
|
||||
)
|
||||
|
||||
po_obj.update_ordered_qty(po_item_rows)
|
||||
if self.get("is_old_subcontracting_flow"):
|
||||
po_obj.update_reserved_qty_for_subcontract()
|
||||
|
||||
def on_submit(self):
|
||||
if self.get("is_return"):
|
||||
@@ -1213,10 +1164,7 @@ class BuyingController(SubcontractingController):
|
||||
if self.doctype == "Material Request":
|
||||
return
|
||||
|
||||
if self.get("is_old_subcontracting_flow"):
|
||||
validate_item_type(self, "is_sub_contracted_item", "subcontracted")
|
||||
else:
|
||||
validate_item_type(self, "is_purchase_item", "purchase")
|
||||
validate_item_type(self, "is_purchase_item", "purchase")
|
||||
|
||||
|
||||
def get_asset_item_details(asset_items):
|
||||
|
||||
@@ -26,17 +26,7 @@ from erpnext.stock.utils import get_incoming_rate
|
||||
class SubcontractingController(StockController):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if self.get("is_old_subcontracting_flow"):
|
||||
self.subcontract_data = frappe._dict(
|
||||
{
|
||||
"order_doctype": "Purchase Order",
|
||||
"order_field": "purchase_order",
|
||||
"rm_detail_field": "po_detail",
|
||||
"receipt_supplied_items_field": "Purchase Receipt Item Supplied",
|
||||
"order_supplied_items_field": "Purchase Order Item Supplied",
|
||||
}
|
||||
)
|
||||
elif self.doctype == "Subcontracting Inward Order":
|
||||
if self.doctype == "Subcontracting Inward Order":
|
||||
self.subcontract_data = frappe._dict(
|
||||
{
|
||||
"order_doctype": "Subcontracting Inward Order",
|
||||
@@ -541,11 +531,7 @@ class SubcontractingController(StockController):
|
||||
self.__set_alternative_item_details(row)
|
||||
|
||||
self.__transferred_items = copy.deepcopy(self.available_materials)
|
||||
if self.get("is_old_subcontracting_flow"):
|
||||
for doctype in ["Purchase Receipt", "Purchase Invoice"]:
|
||||
self.__update_consumed_materials(doctype)
|
||||
else:
|
||||
self.__update_consumed_materials("Subcontracting Receipt")
|
||||
self.__update_consumed_materials("Subcontracting Receipt")
|
||||
|
||||
def __remove_changed_rows(self):
|
||||
if not self.__changed_name:
|
||||
@@ -1136,24 +1122,17 @@ class SubcontractingController(StockController):
|
||||
|
||||
def set_consumed_qty_in_subcontract_order(self):
|
||||
# Update consumed qty back in the subcontract order
|
||||
if self.doctype in ["Subcontracting Order", "Subcontracting Receipt"] or self.get(
|
||||
"is_old_subcontracting_flow"
|
||||
):
|
||||
if self.doctype in ["Subcontracting Order", "Subcontracting Receipt"]:
|
||||
self.__get_subcontract_orders()
|
||||
itemwise_consumed_qty = defaultdict(float)
|
||||
if self.get("is_old_subcontracting_flow"):
|
||||
doctypes = ["Purchase Receipt", "Purchase Invoice"]
|
||||
else:
|
||||
doctypes = ["Subcontracting Receipt"]
|
||||
|
||||
for doctype in doctypes:
|
||||
consumed_items, receipt_items = self.__update_consumed_materials(
|
||||
doctype, return_consumed_items=True
|
||||
)
|
||||
consumed_items, receipt_items = self.__update_consumed_materials(
|
||||
"Subcontracting Receipt", return_consumed_items=True
|
||||
)
|
||||
|
||||
for row in consumed_items:
|
||||
key = (row.rm_item_code, row.main_item_code, receipt_items.get(row.reference_name))
|
||||
itemwise_consumed_qty[key] += row.consumed_qty
|
||||
for row in consumed_items:
|
||||
key = (row.rm_item_code, row.main_item_code, receipt_items.get(row.reference_name))
|
||||
itemwise_consumed_qty[key] += row.consumed_qty
|
||||
|
||||
self.__update_consumed_qty_in_subcontract_order(itemwise_consumed_qty)
|
||||
|
||||
@@ -1236,32 +1215,10 @@ class SubcontractingController(StockController):
|
||||
via_landed_cost_voucher=via_landed_cost_voucher,
|
||||
)
|
||||
|
||||
def get_supplied_items_cost(self, item_row_id, reset_outgoing_rate=True):
|
||||
def get_supplied_items_cost(self, item_row_id):
|
||||
supplied_items_cost = 0.0
|
||||
for item in self.get("supplied_items"):
|
||||
if item.reference_name == item_row_id:
|
||||
if (
|
||||
self.get("is_old_subcontracting_flow")
|
||||
and reset_outgoing_rate
|
||||
and frappe.get_cached_value("Item", item.rm_item_code, "is_stock_item")
|
||||
):
|
||||
rate = get_incoming_rate(
|
||||
{
|
||||
"item_code": item.rm_item_code,
|
||||
"warehouse": self.supplier_warehouse,
|
||||
"posting_date": self.posting_date,
|
||||
"posting_time": self.posting_time,
|
||||
"qty": -1 * item.consumed_qty,
|
||||
"voucher_detail_no": item.name,
|
||||
"serial_and_batch_bundle": item.get("serial_and_batch_bundle"),
|
||||
"serial_no": item.get("serial_no"),
|
||||
"batch_no": item.get("batch_no"),
|
||||
}
|
||||
)
|
||||
|
||||
if rate > 0:
|
||||
item.rate = rate
|
||||
|
||||
item.amount = flt(flt(item.consumed_qty) * flt(item.rate), item.precision("amount"))
|
||||
supplied_items_cost += item.amount
|
||||
|
||||
|
||||
@@ -92,11 +92,7 @@ erpnext.buying = {
|
||||
this.frm.set_query("item_code", "items", function () {
|
||||
if (me.frm.doc.is_subcontracted) {
|
||||
var filters = { supplier: me.frm.doc.supplier };
|
||||
if (me.frm.doc.is_old_subcontracting_flow) {
|
||||
filters["is_sub_contracted_item"] = 1;
|
||||
} else {
|
||||
filters["is_stock_item"] = 0;
|
||||
}
|
||||
filters["is_stock_item"] = 0;
|
||||
|
||||
return {
|
||||
query: "erpnext.controllers.queries.item_query",
|
||||
|
||||
@@ -806,7 +806,6 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
item_tax_template: item.item_tax_template,
|
||||
child_doctype: item.doctype,
|
||||
child_docname: item.name,
|
||||
is_old_subcontracting_flow: me.frm.doc.is_old_subcontracting_flow,
|
||||
use_serial_batch_fields: item.use_serial_batch_fields,
|
||||
serial_and_batch_bundle: item.serial_and_batch_bundle,
|
||||
},
|
||||
|
||||
@@ -744,11 +744,7 @@ erpnext.utils.update_child_items = function (opts) {
|
||||
}
|
||||
} else if (frm.doc.doctype == "Purchase Order") {
|
||||
if (frm.doc.is_subcontracted) {
|
||||
if (frm.doc.is_old_subcontracting_flow) {
|
||||
filters = { is_sub_contracted_item: 1 };
|
||||
} else {
|
||||
filters = { is_stock_item: 0 };
|
||||
}
|
||||
filters = { is_stock_item: 0 };
|
||||
} else {
|
||||
filters = { is_purchase_item: 1 };
|
||||
}
|
||||
@@ -791,7 +787,6 @@ erpnext.utils.update_child_items = function (opts) {
|
||||
pos_profile: cint(frm.doc.is_pos) ? frm.doc.pos_profile : "",
|
||||
tax_category: frm.doc.tax_category,
|
||||
child_doctype: frm.doc.doctype + " Item",
|
||||
is_old_subcontracting_flow: frm.doc.is_old_subcontracting_flow,
|
||||
},
|
||||
},
|
||||
callback: function (r) {
|
||||
@@ -904,11 +899,7 @@ erpnext.utils.update_child_items = function (opts) {
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
["Purchase Order", "Sales Order"].includes(frm.doc.doctype) &&
|
||||
frm.doc.is_subcontracted &&
|
||||
!frm.doc.is_old_subcontracting_flow
|
||||
) {
|
||||
if (["Purchase Order", "Sales Order"].includes(frm.doc.doctype) && frm.doc.is_subcontracted) {
|
||||
fields.push(
|
||||
{
|
||||
fieldtype: "Link",
|
||||
|
||||
@@ -572,7 +572,9 @@ def _make_customer(source_name, ignore_permissions=False):
|
||||
if quotation.quotation_to == "Customer":
|
||||
return frappe.get_doc("Customer", quotation.party_name)
|
||||
elif quotation.quotation_to == "CRM Deal":
|
||||
return frappe.get_doc("Customer", {"crm_deal": quotation.party_name})
|
||||
customer_name = frappe.get_value("Customer", {"crm_deal": quotation.party_name})
|
||||
if customer_name:
|
||||
return frappe.get_doc("Customer", customer_name)
|
||||
|
||||
# Check if a Customer already exists for the Lead or Prospect.
|
||||
existing_customer = None
|
||||
|
||||
@@ -145,11 +145,7 @@ class Bin(Document):
|
||||
# reserved qty
|
||||
|
||||
subcontract_order = frappe.qb.DocType(subcontract_doctype)
|
||||
supplied_item = frappe.qb.DocType(
|
||||
"Purchase Order Item Supplied"
|
||||
if subcontract_doctype == "Purchase Order"
|
||||
else "Subcontracting Order Supplied Item"
|
||||
)
|
||||
supplied_item = frappe.qb.DocType("Subcontracting Order Supplied Item")
|
||||
|
||||
conditions = (
|
||||
(supplied_item.rm_item_code == self.item_code)
|
||||
@@ -157,11 +153,7 @@ class Bin(Document):
|
||||
& (subcontract_order.per_received < 100)
|
||||
& (supplied_item.reserve_warehouse == self.warehouse)
|
||||
& (
|
||||
(
|
||||
(subcontract_order.is_old_subcontracting_flow == 1)
|
||||
& (subcontract_order.status != "Closed")
|
||||
& (subcontract_order.docstatus == 1)
|
||||
)
|
||||
((subcontract_order.status != "Closed") & (subcontract_order.docstatus == 1))
|
||||
if subcontract_doctype == "Purchase Order"
|
||||
else (subcontract_order.docstatus == 1)
|
||||
)
|
||||
@@ -193,7 +185,6 @@ class Bin(Document):
|
||||
(
|
||||
(Coalesce(se.purchase_order, "") != "")
|
||||
& (subcontract_order.name == se.purchase_order)
|
||||
& (subcontract_order.is_old_subcontracting_flow == 1)
|
||||
& (subcontract_order.status != "Closed")
|
||||
)
|
||||
if subcontract_doctype == "Purchase Order"
|
||||
|
||||
@@ -278,8 +278,6 @@ erpnext.stock.PurchaseReceiptController = class PurchaseReceiptController extend
|
||||
if (this.frm.doc.docstatus == 1 && this.frm.doc.status === "Closed" && this.frm.has_perm("submit")) {
|
||||
cur_frm.add_custom_button(__("Reopen"), this.reopen_purchase_receipt, __("Status"));
|
||||
}
|
||||
|
||||
this.frm.toggle_reqd("supplier_warehouse", this.frm.doc.is_old_subcontracting_flow);
|
||||
}
|
||||
|
||||
make_purchase_invoice() {
|
||||
@@ -420,14 +418,6 @@ cur_frm.fields_dict["items"].grid.get_field("bom").get_query = function (doc, cd
|
||||
|
||||
frappe.provide("erpnext.buying");
|
||||
|
||||
frappe.ui.form.on("Purchase Receipt", "is_subcontracted", function (frm) {
|
||||
if (frm.doc.is_old_subcontracting_flow) {
|
||||
erpnext.buying.get_default_bom(frm);
|
||||
}
|
||||
|
||||
frm.toggle_reqd("supplier_warehouse", frm.doc.is_old_subcontracting_flow);
|
||||
});
|
||||
|
||||
frappe.ui.form.on("Purchase Receipt Item", {
|
||||
item_code: function (frm, cdt, cdn) {
|
||||
var d = locals[cdt][cdn];
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_auto_repeat": 1,
|
||||
"allow_bulk_edit": 1,
|
||||
"allow_import": 1,
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2026-04-06 14:10:33.384946",
|
||||
@@ -155,7 +156,6 @@
|
||||
"remarks",
|
||||
"range",
|
||||
"amended_from",
|
||||
"is_old_subcontracting_flow",
|
||||
"other_details",
|
||||
"connections_tab"
|
||||
],
|
||||
@@ -1151,14 +1151,6 @@
|
||||
"fieldname": "dimension_col_break",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_old_subcontracting_flow",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Is Old Subcontracting Flow",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "address_and_contact_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
@@ -1306,7 +1298,7 @@
|
||||
"idx": 261,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-04-06 14:11:29.630333",
|
||||
"modified": "2026-05-04 10:19:44.638858",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Purchase Receipt",
|
||||
|
||||
@@ -88,7 +88,6 @@ class PurchaseReceipt(BuyingController):
|
||||
instructions: DF.SmallText | None
|
||||
inter_company_reference: DF.Link | None
|
||||
is_internal_supplier: DF.Check
|
||||
is_old_subcontracting_flow: DF.Check
|
||||
is_return: DF.Check
|
||||
is_subcontracted: DF.Check
|
||||
item_wise_tax_details: DF.Table[ItemWiseTaxDetail]
|
||||
@@ -149,6 +148,7 @@ class PurchaseReceipt(BuyingController):
|
||||
taxes_and_charges_deducted: DF.Currency
|
||||
tc_name: DF.Link | None
|
||||
terms: DF.TextEditor | None
|
||||
title: DF.Data | None
|
||||
total: DF.Currency
|
||||
total_net_weight: DF.Float
|
||||
total_qty: DF.Float
|
||||
|
||||
@@ -106,9 +106,6 @@
|
||||
"rejected_serial_no",
|
||||
"column_break_tolu",
|
||||
"batch_no",
|
||||
"subcontract_bom_section",
|
||||
"include_exploded_items",
|
||||
"bom",
|
||||
"item_weight_details",
|
||||
"weight_per_unit",
|
||||
"total_weight",
|
||||
@@ -635,26 +632,6 @@
|
||||
"no_copy": 1,
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.is_old_subcontracting_flow",
|
||||
"fieldname": "bom",
|
||||
"fieldtype": "Link",
|
||||
"label": "BOM",
|
||||
"no_copy": 1,
|
||||
"options": "BOM",
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"read_only_depends_on": "eval:!parent.is_old_subcontracting_flow"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:parent.is_subcontracted",
|
||||
"fieldname": "include_exploded_items",
|
||||
"fieldtype": "Check",
|
||||
"label": "Include Exploded Items",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "billed_amt",
|
||||
"fieldtype": "Currency",
|
||||
@@ -1015,12 +992,6 @@
|
||||
"print_hide": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.is_old_subcontracting_flow",
|
||||
"fieldname": "subcontract_bom_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Subcontract BOM"
|
||||
},
|
||||
{
|
||||
"fieldname": "serial_no",
|
||||
"fieldtype": "Text",
|
||||
@@ -1149,7 +1120,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-04-29 16:01:34.154697",
|
||||
"modified": "2026-05-04 10:52:43.188507",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Purchase Receipt Item",
|
||||
|
||||
@@ -28,7 +28,6 @@ class PurchaseReceiptItem(Document):
|
||||
base_rate_with_margin: DF.Currency
|
||||
batch_no: DF.Link | None
|
||||
billed_amt: DF.Currency
|
||||
bom: DF.Link | None
|
||||
brand: DF.Link | None
|
||||
conversion_factor: DF.Float
|
||||
cost_center: DF.Link | None
|
||||
@@ -41,7 +40,6 @@ class PurchaseReceiptItem(Document):
|
||||
from_warehouse: DF.Link | None
|
||||
has_item_scanned: DF.Check
|
||||
image: DF.Attach | None
|
||||
include_exploded_items: DF.Check
|
||||
is_fixed_asset: DF.Check
|
||||
is_free_item: DF.Check
|
||||
item_code: DF.Link
|
||||
|
||||
@@ -1200,16 +1200,6 @@ erpnext.stock.StockEntry = class StockEntry extends erpnext.stock.StockControlle
|
||||
return erpnext.queries.item({ is_stock_item: 1 });
|
||||
};
|
||||
|
||||
this.frm.set_query("purchase_order", function () {
|
||||
return {
|
||||
filters: {
|
||||
docstatus: 1,
|
||||
is_old_subcontracting_flow: 1,
|
||||
company: me.frm.doc.company,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
this.frm.set_query("subcontracting_order", function () {
|
||||
return {
|
||||
filters: {
|
||||
|
||||
@@ -128,7 +128,6 @@ class StockEntry(StockController, SubcontractingInwardController):
|
||||
process_loss_percentage: DF.Percent
|
||||
process_loss_qty: DF.Float
|
||||
project: DF.Link | None
|
||||
purchase_order: DF.Link | None
|
||||
purchase_receipt_no: DF.Link | None
|
||||
purpose: DF.Literal[
|
||||
"Material Issue",
|
||||
@@ -173,16 +172,7 @@ class StockEntry(StockController, SubcontractingInwardController):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if self.purchase_order:
|
||||
self.subcontract_data = frappe._dict(
|
||||
{
|
||||
"order_doctype": "Purchase Order",
|
||||
"order_field": "purchase_order",
|
||||
"rm_detail_field": "po_detail",
|
||||
"order_supplied_items_field": "Purchase Order Item Supplied",
|
||||
}
|
||||
)
|
||||
elif self.subcontracting_inward_order:
|
||||
if self.subcontracting_inward_order:
|
||||
self.subcontract_data = frappe._dict(
|
||||
{
|
||||
"order_doctype": "Subcontracting Inward Order",
|
||||
@@ -253,7 +243,6 @@ class StockEntry(StockController, SubcontractingInwardController):
|
||||
self.validate_source_stock_entry()
|
||||
self.validate_bom()
|
||||
self.set_process_loss_qty()
|
||||
self.validate_purchase_order()
|
||||
self.validate_company_in_accounting_dimension()
|
||||
|
||||
if self.purpose in ("Manufacture", "Repack"):
|
||||
@@ -1696,19 +1685,6 @@ class StockEntry(StockController, SubcontractingInwardController):
|
||||
item_code = d.original_item or d.item_code
|
||||
validate_bom_no(item_code, d.bom_no)
|
||||
|
||||
def validate_purchase_order(self):
|
||||
if self.purpose == "Send to Subcontractor" and self.get("purchase_order"):
|
||||
is_old_subcontracting_flow = frappe.db.get_value(
|
||||
"Purchase Order", self.purchase_order, "is_old_subcontracting_flow"
|
||||
)
|
||||
|
||||
if not is_old_subcontracting_flow:
|
||||
frappe.throw(
|
||||
_("Please select Subcontracting Order instead of Purchase Order {0}").format(
|
||||
self.purchase_order
|
||||
)
|
||||
)
|
||||
|
||||
def validate_closed_subcontracting_order(self):
|
||||
order = self.get("subcontracting_order") or self.get("subcontracting_inward_order")
|
||||
if order:
|
||||
|
||||
@@ -346,13 +346,8 @@ def validate_item_details(ctx: ItemDetailsCtx, item):
|
||||
throw(_(msg), title=_("Template Item Selected"))
|
||||
|
||||
elif ctx.doctype != "Material Request":
|
||||
if ctx.is_subcontracted:
|
||||
if ctx.is_old_subcontracting_flow:
|
||||
if item.is_sub_contracted_item != 1:
|
||||
throw(_("Item {0} must be a Sub-contracted Item").format(item.name))
|
||||
else:
|
||||
if item.is_stock_item:
|
||||
throw(_("Item {0} must be a Non-Stock Item").format(item.name))
|
||||
if ctx.is_subcontracted and item.is_stock_item:
|
||||
throw(_("Item {0} must be a Non-Stock Item").format(item.name))
|
||||
|
||||
|
||||
def get_basic_details(ctx: ItemDetailsCtx, item, overwrite_warehouse=True) -> ItemDetails:
|
||||
@@ -1134,6 +1129,7 @@ def insert_item_price(ctx: ItemDetailsCtx):
|
||||
currency=ctx.currency,
|
||||
uom=ctx.stock_uom,
|
||||
price_list=ctx.price_list,
|
||||
valid_from=transaction_date,
|
||||
)
|
||||
item_price.insert()
|
||||
frappe.msgprint(
|
||||
@@ -1156,6 +1152,7 @@ def insert_item_price(ctx: ItemDetailsCtx):
|
||||
"currency": ctx.currency,
|
||||
"price_list_rate": price_list_rate,
|
||||
"uom": ctx.stock_uom,
|
||||
"valid_from": transaction_date,
|
||||
}
|
||||
)
|
||||
item_price.insert()
|
||||
|
||||
@@ -55,7 +55,6 @@ frappe.ui.form.on("Subcontracting Order", {
|
||||
filters: {
|
||||
docstatus: 1,
|
||||
is_subcontracted: 1,
|
||||
is_old_subcontracting_flow: 0,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -138,9 +138,6 @@ class SubcontractingOrder(SubcontractingController):
|
||||
if not po.is_subcontracted:
|
||||
frappe.throw(_("Please select a valid Purchase Order that is configured for Subcontracting."))
|
||||
|
||||
if po.is_old_subcontracting_flow:
|
||||
frappe.throw(_("Please select a valid Purchase Order that has Service Items."))
|
||||
|
||||
if po.docstatus != 1:
|
||||
msg = f"Please submit Purchase Order {po.name} before proceeding."
|
||||
frappe.throw(_(msg))
|
||||
|
||||
@@ -1076,7 +1076,6 @@ def make_purchase_receipt(
|
||||
"subcontracting_receipt": source_doc.name,
|
||||
"supplier_warehouse": source_doc.supplier_warehouse,
|
||||
"is_subcontracted": 1,
|
||||
"is_old_subcontracting_flow": 0,
|
||||
"currency": frappe.get_cached_value("Company", target.company, "default_currency"),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -343,7 +343,6 @@ class TransactionBase(StatusUpdater):
|
||||
"item_tax_template": item.get("item_tax_template"),
|
||||
"child_doctype": item.get("doctype"),
|
||||
"child_docname": item.get("name"),
|
||||
"is_old_subcontracting_flow": self.get("is_old_subcontracting_flow"),
|
||||
}
|
||||
),
|
||||
self,
|
||||
|
||||
Reference in New Issue
Block a user