From 3eaecde8760843915a9764d15058b002281fd04f Mon Sep 17 00:00:00 2001 From: saeed Date: Thu, 29 Oct 2026 18:43:22 +0330 Subject: [PATCH] feat: port QA/money fixes from tehran_taj (atomic document confirm, validated inputs, expert customer edit/toggle/weights, made-to-order, grid sort/customer column) Order-revision workflow kept as is: the direct admin line-edit routes were not ported. Co-Authored-By: Claude Sonnet 5.5 --- .../src/components/x-table/XTable.vue | 10 +- apps/admin-panel/src/utils/select.ts | 14 + .../src/views/apps/category/Add.vue | 3 +- .../src/views/apps/customer/bulkCustomer.vue | 3 +- .../src/views/apps/product/AddProduct.vue | 3 +- .../src/views/apps/product/ChangePercent.vue | 3 +- .../src/views/branch/AddBranches.vue | 3 +- .../src/views/branch/order/create.vue | 9 +- .../src/views/finical/addFinDoc.vue | 3 +- .../src/views/invoice/AddInvoice.vue | 3 +- .../admin-panel/src/views/order/MakeOrder.vue | 2 + apps/admin-panel/src/views/order/OmdeList.vue | 16 + .../src/views/order/OmdeListModel.ts | 4 +- .../src/views/order/TaminListModel.ts | 3 +- apps/admin-panel/src/views/order/create.vue | 14 +- .../src/views/promotion/PromotionForm.vue | 3 +- apps/admin-panel/src/views/statics/index.vue | 3 +- .../migration.sql | 3 + apps/api/prisma/schema.prisma | 3 + apps/api/src/app.module.ts | 2 + apps/api/src/common/customer-input.ts | 32 ++ .../accounting-hooks.service.spec.ts | 55 ++++ .../accounting-hooks.service.ts | 46 +++ .../accounting-sync.service.ts | 3 +- .../accounting-sync/document-check.spec.ts | 43 +++ .../modules/accounting-sync/document-check.ts | 17 ++ .../modules/accounting/ledger.service.spec.ts | 158 +++++++++- .../src/modules/accounting/ledger.service.ts | 146 ++++++--- .../accounting/reference-parity.spec.ts | 277 +++++++++++++++++ .../admin-customers.service.ts | 28 +- .../admin-documents.service.spec.ts | 192 ++++++++++++ .../admin-documents.service.ts | 173 ++++++++--- .../admin-orders/admin-orders.controller.ts | 2 +- .../admin-orders/admin-orders.module.ts | 3 +- .../admin-orders/admin-orders.service.spec.ts | 215 ++++++++++++++ .../admin-orders/admin-orders.service.ts | 56 +++- .../admin-preorders.service.spec.ts | 68 +++++ .../admin-preorders.service.ts | 15 +- .../modules/admin-table/table.service.spec.ts | 26 ++ .../src/modules/admin-table/table.service.ts | 7 +- .../customer-preorders.controller.ts | 31 ++ .../customer-preorders.module.ts | 12 + .../customer-preorders.service.spec.ts | 87 ++++++ .../customer-preorders.service.ts | 129 ++++++++ .../modules/orders/dto/create-order.dto.ts | 4 +- .../modules/orders/order-corrections.spec.ts | 280 ++++++++++++++++++ apps/api/src/modules/orders/orders.service.ts | 237 +++++++++++++-- .../src/modules/pricing/wage.service.spec.ts | 74 +++++ apps/api/src/modules/pricing/wage.service.ts | 32 +- .../modules/sales-agent/agent-orders.spec.ts | 23 ++ .../src/modules/sales-agent/agent-orders.ts | 22 +- .../sales-agent/sales-agent.service.ts | 3 +- .../sales-expert-app.controller.ts | 37 ++- .../sales-expert-app.customers.spec.ts | 197 ++++++++++++ .../sales-expert-app.module.ts | 3 +- .../sales-expert-app.service.ts | 190 +++++++++++- apps/user-app/src/api/client.ts | 74 +++++ apps/user-app/src/router/index.ts | 4 + apps/user-app/src/views/CartView.vue | 14 + .../src/views/expert/ChildOrdersView.vue | 208 ++++++++++++- .../src/views/expert/CustomerChoiceView.vue | 212 ++++++++++++- .../user-app/src/views/user/DashboardView.vue | 44 +++ .../src/views/user/MakeOrderStatusView.vue | 237 +++++++++++++++ .../user-app/src/views/user/MakeOrderView.vue | 197 ++++++++++++ apps/user-app/src/views/user/OrdersView.vue | 6 +- 65 files changed, 3810 insertions(+), 216 deletions(-) create mode 100644 apps/admin-panel/src/utils/select.ts create mode 100644 apps/api/prisma/migrations/20261029120000_preorder_quantity/migration.sql create mode 100644 apps/api/src/common/customer-input.ts create mode 100644 apps/api/src/modules/accounting-sync/document-check.spec.ts create mode 100644 apps/api/src/modules/accounting-sync/document-check.ts create mode 100644 apps/api/src/modules/accounting/reference-parity.spec.ts create mode 100644 apps/api/src/modules/admin-documents/admin-documents.service.spec.ts create mode 100644 apps/api/src/modules/admin-orders/admin-orders.service.spec.ts create mode 100644 apps/api/src/modules/admin-preorders/admin-preorders.service.spec.ts create mode 100644 apps/api/src/modules/admin-table/table.service.spec.ts create mode 100644 apps/api/src/modules/customer-preorders/customer-preorders.controller.ts create mode 100644 apps/api/src/modules/customer-preorders/customer-preorders.module.ts create mode 100644 apps/api/src/modules/customer-preorders/customer-preorders.service.spec.ts create mode 100644 apps/api/src/modules/customer-preorders/customer-preorders.service.ts create mode 100644 apps/api/src/modules/orders/order-corrections.spec.ts create mode 100644 apps/api/src/modules/pricing/wage.service.spec.ts create mode 100644 apps/api/src/modules/sales-agent/agent-orders.spec.ts create mode 100644 apps/api/src/modules/sales-expert-app/sales-expert-app.customers.spec.ts create mode 100644 apps/user-app/src/views/user/MakeOrderStatusView.vue create mode 100644 apps/user-app/src/views/user/MakeOrderView.vue diff --git a/apps/admin-panel/src/components/x-table/XTable.vue b/apps/admin-panel/src/components/x-table/XTable.vue index 1554ff1..4462285 100644 --- a/apps/admin-panel/src/components/x-table/XTable.vue +++ b/apps/admin-panel/src/components/x-table/XTable.vue @@ -303,9 +303,13 @@ const filterableOptions = computed(() => ); /** legacy `order` computed: '+field' / '-field' */ -const order = computed(() => - sort.value ? `${sortDirection.value === 'asc' ? '+' : '-'}${sort.value}` : undefined, -); +const order = computed(() => { + if (!sort.value) return undefined; + // A column may be keyed by the reference's name for it; its filter key, when it + // has one, is the real field it stands for, so that is what is sorted on. + const col = cols.value.find((c) => c.key === sort.value); + return `${sortDirection.value === 'asc' ? '+' : '-'}${col?.__fkey ?? sort.value}`; +}); const antColumns = computed(() => visibleCols.value.map((c) => ({ diff --git a/apps/admin-panel/src/utils/select.ts b/apps/admin-panel/src/utils/select.ts new file mode 100644 index 0000000..de82adf --- /dev/null +++ b/apps/admin-panel/src/utils/select.ts @@ -0,0 +1,14 @@ +/** + * The text of a slot-style ``, for a custom `filter-option`. + * + * ant-design-vue 4 hands `filterOption` the option's default slot as a + * function, not as an array of vnodes (the shape the legacy filters indexed + * into), so `option.children[0].children` is always undefined and a typed + * search matched nothing. This calls the slot first. + */ +export function optionText(option: any): string { + const children = typeof option?.children === 'function' ? option.children() : option?.children; + const first = Array.isArray(children) ? children[0] : children; + if (typeof first === 'string') return first; + return String(first?.children ?? ''); +} diff --git a/apps/admin-panel/src/views/apps/category/Add.vue b/apps/admin-panel/src/views/apps/category/Add.vue index c0ad617..9acf85a 100644 --- a/apps/admin-panel/src/views/apps/category/Add.vue +++ b/apps/admin-panel/src/views/apps/category/Add.vue @@ -136,6 +136,7 @@ @@ -392,6 +520,80 @@ onMounted(load) } /* Secondary now that "enter the account" is the primary action here. */ +.cch__actions { + display: flex; + gap: 10px; + margin-top: 10px; +} + +.cch__act { + flex: 1; + padding: 12px; + border: 1px solid @wine; + border-radius: 14px; + background: transparent; + color: @wine; + font-family: inherit; + font-size: 13.5px; + font-weight: 700; + cursor: pointer; + + &.is-danger { + border-color: @red; + color: @red; + } + + &:disabled { + opacity: 0.5; + cursor: default; + } +} + +.cch__confirm { + display: flex; + align-items: center; + gap: 10px; + margin: 12px 0 0; + font-size: 13px; + color: @inkSoft; +} + +.cch__link { + border: none; + background: transparent; + color: @inkFaint; + font-family: inherit; + font-size: 12.5px; + cursor: pointer; +} + +.cch__field { + display: flex; + flex-direction: column; + gap: 5px; + padding: 8px 0; + font-size: 12.5px; + color: @inkSoft; +} + +.cch__field input, +.cch__field textarea { + width: 100%; + padding: 10px 12px; + border: 1px solid @hairline; + border-radius: 12px; + background: transparent; + color: @ink; + font-family: inherit; + font-size: 14px; +} + +.cch__error { + margin: 8px 0 0; + font-size: 12.5px; + color: @red; +} + .cch__close { margin-top: 10px; width: 100%; diff --git a/apps/user-app/src/views/user/DashboardView.vue b/apps/user-app/src/views/user/DashboardView.vue index 5a85dcc..0c34139 100644 --- a/apps/user-app/src/views/user/DashboardView.vue +++ b/apps/user-app/src/views/user/DashboardView.vue @@ -198,6 +198,14 @@
بیخیال
+ + + +
+ + +
+
@@ -205,6 +213,7 @@ import { brand } from '../../brand' import { computed, onActivated, onMounted, reactive, ref } from 'vue' import { useRouter } from 'vue-router' +import { Drawer } from 'ant-design-vue' import AppPullRefresh from '../../components/ui/AppPullRefresh.vue' import Icon from '../../components/icon/Icon.vue' import SectionHead from '../../components/home/SectionHead.vue' @@ -257,6 +266,8 @@ const BASE_TILES = [ // gated on hasMCode() (Dashboard.vue:179, :449). The target has no mcode, so // every signed-in customer sees it and the server scopes it to their token. { id: 6, icon: 'vuesax-linear:chart-2', title: 'گزارش', tone: 'slate', disabled: false }, + // «سفارش ساخت» (reference makeOrder): ask for a piece to be made. + { id: 8, icon: 'vuesax-linear:3d-cube-scan', title: 'سفارش ساخت', tone: 'rose', disabled: false }, ] /** @@ -297,6 +308,15 @@ function goToDetails(id: number) { if (id === 4) router.push({ name: 'ChildOrder' }) if (id === 5) router.push({ name: 'customerChoice' }) if (id === 6) router.push({ name: 'report' }) + if (id === 8) makeOrderChoice.value = true +} + +// «سفارش ساخت»: the reference opened a two-button dialog — place a request, or +// see the ones already placed. +const makeOrderChoice = ref(false) +function chooseMakeOrder(name: 'makeOrderList' | 'makeOrderStatus') { + makeOrderChoice.value = false + router.push({ name }) } async function logOut() { @@ -756,4 +776,28 @@ onMounted(async () => { color: @inkFaint; opacity: 0.7; } + +.dash__choice { + display: flex; + flex-direction: column; + gap: 10px; + padding: 20px 16px 24px; +} + +.dash__choiceBtn { + padding: 15px; + border: 1px solid @wine; + border-radius: 14px; + background: @wine; + color: #fff; + font-family: inherit; + font-size: 15px; + font-weight: 700; + cursor: pointer; +} + +.dash__choiceBtn.is-quiet { + background: transparent; + color: @wine; +} diff --git a/apps/user-app/src/views/user/MakeOrderStatusView.vue b/apps/user-app/src/views/user/MakeOrderStatusView.vue new file mode 100644 index 0000000..dcd63da --- /dev/null +++ b/apps/user-app/src/views/user/MakeOrderStatusView.vue @@ -0,0 +1,237 @@ + + + + + + diff --git a/apps/user-app/src/views/user/MakeOrderView.vue b/apps/user-app/src/views/user/MakeOrderView.vue new file mode 100644 index 0000000..1c546f3 --- /dev/null +++ b/apps/user-app/src/views/user/MakeOrderView.vue @@ -0,0 +1,197 @@ + +