fix(money,orders): wage/gram-debt parity, atomic document confirm, expert edit, made-to-order, QA fixes
- Rial wage and gram-debt arithmetic matched to the reference; order corrections keep debt in sync - Document confirm/un-confirm is a compare-and-swap (no double-applied cache under concurrency) - Settlement document and expert inputs validated (400 instead of 500) - Expert: edit / activate-deactivate own customers, adjust raw weights only - Made-to-order flow in the app, PreOrder.quantity migration - Order grids: customer column, sort keys; panel select search; pre-order quantity save - Rebuilt release artifacts Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
959a272227
commit
6514b05d18
@@ -0,0 +1,197 @@
|
||||
<!--
|
||||
"سفارش ساخت" — ask for a piece to be made.
|
||||
|
||||
Ported from henza_pwa/src/views/user/src/components/makeOrder.vue (route
|
||||
/makeOrder, name makeOrderList), restyled into this app's design language.
|
||||
|
||||
Kept from the reference:
|
||||
- the header title "سفارش ساخت"
|
||||
- the photo of the piece first, then the questions: رنگ محصول، سایز محصول،
|
||||
رنگ مینا، رنگ سنگ، تعداد، توضیحات (the last one optional)
|
||||
- every field but the description required, with the reference's own messages
|
||||
- POST create-custom-order -> customer/me/pre-orders here; the customer is the
|
||||
token's, nothing about WHO is sent
|
||||
The reference then sent the customer home; this goes to «وضعیت سفارش» so the
|
||||
request just placed is the first thing seen.
|
||||
-->
|
||||
<template>
|
||||
<div class="mko">
|
||||
<PageBar title="سفارش ساخت" back-to="userPage::hasSubRoute" />
|
||||
|
||||
<Spinner :spinning="submitting" tip="در حال ثبت اطلاعات">
|
||||
<container class="mko__body">
|
||||
<form class="mko__form" novalidate @submit.prevent="submit">
|
||||
<SingleImageUploader
|
||||
upload-text="بارگذاری عکس سفارش"
|
||||
purpose="preorder-design"
|
||||
@done="(id: string) => (form.file_id = id)"
|
||||
/>
|
||||
|
||||
<p class="mko__lead">لطفا به سوالات زیر پاسخ دهید</p>
|
||||
|
||||
<label class="mko__field">
|
||||
<span>رنگ محصول</span>
|
||||
<input v-model.trim="form.color" type="text" maxlength="100" />
|
||||
</label>
|
||||
<label class="mko__field">
|
||||
<span>سایز محصول</span>
|
||||
<input v-model.trim="form.size" type="text" maxlength="100" />
|
||||
</label>
|
||||
<label class="mko__field">
|
||||
<span>رنگ مینا</span>
|
||||
<input v-model.trim="form.color_mina" type="text" maxlength="100" />
|
||||
</label>
|
||||
<label class="mko__field">
|
||||
<span>رنگ سنگ</span>
|
||||
<input v-model.trim="form.color_stone" type="text" maxlength="100" />
|
||||
</label>
|
||||
<label class="mko__field">
|
||||
<span>تعداد</span>
|
||||
<input v-model.number="form.sum" type="number" inputmode="numeric" min="1" step="1" />
|
||||
</label>
|
||||
<label class="mko__field">
|
||||
<span>توضیحات</span>
|
||||
<textarea v-model.trim="form.description" rows="3" maxlength="1000" />
|
||||
</label>
|
||||
|
||||
<p v-if="error" class="mko__error">{{ error }}</p>
|
||||
|
||||
<button type="submit" class="mko__submit" :disabled="submitting">ارسال اطلاعات</button>
|
||||
</form>
|
||||
</container>
|
||||
</Spinner>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { toast } from '../../composables/useToast'
|
||||
import PageBar from '../../components/ui/PageBar.vue'
|
||||
import Spinner from '../../components/Spinner.vue'
|
||||
import SingleImageUploader from '../../components/upload/SingleImageUploader.vue'
|
||||
import { api } from '../../api/client'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const form = reactive({
|
||||
file_id: null as string | null,
|
||||
color: '',
|
||||
size: '',
|
||||
color_mina: '',
|
||||
color_stone: '',
|
||||
sum: 1,
|
||||
description: '',
|
||||
})
|
||||
|
||||
const submitting = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
/** A message about a missing field must not outlive the fix (e.g. the photo being uploaded). */
|
||||
watch(form, () => (error.value = null))
|
||||
|
||||
/** The reference's required-field messages, in the order it asked. */
|
||||
function firstMissing(): string | null {
|
||||
if (!form.file_id) return 'لطفا تصویر سفارش را آپلود کنید'
|
||||
if (!form.color) return 'لطفا رنگ محصول را وارد کنید'
|
||||
if (!form.size) return 'لطفا سایز محصول را وارد کنید'
|
||||
if (!form.color_mina) return 'لطفا رنگ مینا را وارد کنید'
|
||||
if (!form.color_stone) return 'لطفا رنگ سنگ را وارد کنید'
|
||||
if (!Number.isInteger(form.sum) || form.sum < 1) return 'لطفا تعداد را وارد کنید'
|
||||
return null
|
||||
}
|
||||
|
||||
async function submit() {
|
||||
error.value = firstMissing()
|
||||
if (error.value) return
|
||||
|
||||
submitting.value = true
|
||||
try {
|
||||
await api.createPreOrder({
|
||||
color: form.color,
|
||||
size: form.size,
|
||||
color_mina: form.color_mina,
|
||||
color_stone: form.color_stone,
|
||||
sum: form.sum,
|
||||
file_id: form.file_id,
|
||||
description: form.description || undefined,
|
||||
})
|
||||
toast.success('سفارش با موفقیت ثبت شد')
|
||||
router.replace({ name: 'makeOrderStatus' })
|
||||
} catch {
|
||||
error.value = 'خطا در ثبت سفارش'
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.mko__body {
|
||||
padding-top: 84px;
|
||||
padding-bottom: 110px;
|
||||
}
|
||||
|
||||
.mko__form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.mko__lead {
|
||||
margin: 18px 2px 4px;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: @inkMuted;
|
||||
}
|
||||
|
||||
.mko__field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid @hairline;
|
||||
font-size: 12.5px;
|
||||
color: @inkSoft;
|
||||
}
|
||||
|
||||
.mko__field input,
|
||||
.mko__field textarea {
|
||||
width: 100%;
|
||||
padding: 11px 12px;
|
||||
border: 1px solid @hairline;
|
||||
border-radius: 12px;
|
||||
background: transparent;
|
||||
color: @ink;
|
||||
font-family: inherit;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.mko__error {
|
||||
margin: 12px 2px 0;
|
||||
font-size: 12.5px;
|
||||
color: @red;
|
||||
}
|
||||
|
||||
.mko__submit {
|
||||
position: fixed;
|
||||
left: 16px;
|
||||
right: 16px;
|
||||
bottom: 20px;
|
||||
z-index: 20;
|
||||
max-width: 736px;
|
||||
margin: 0 auto;
|
||||
padding: 15px;
|
||||
border: none;
|
||||
border-radius: 999px;
|
||||
background: @wine;
|
||||
color: #fff;
|
||||
font-family: inherit;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mko__submit:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: progress;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user